Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac7eb6d85d | |||
| 728847a8e3 |
@@ -5,6 +5,7 @@ import com.easycard.common.auth.JwtTokenService;
|
|||||||
import com.easycard.common.auth.LoginUser;
|
import com.easycard.common.auth.LoginUser;
|
||||||
import com.easycard.common.tenant.TenantContext;
|
import com.easycard.common.tenant.TenantContext;
|
||||||
import com.easycard.common.tenant.TenantContextHolder;
|
import com.easycard.common.tenant.TenantContextHolder;
|
||||||
|
import com.easycard.module.tenant.web.MiniappTenantContextFilter;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
@@ -83,7 +84,8 @@ public class SecurityConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(
|
public SecurityFilterChain securityFilterChain(
|
||||||
HttpSecurity http,
|
HttpSecurity http,
|
||||||
JwtAuthenticationFilter jwtAuthenticationFilter
|
JwtAuthenticationFilter jwtAuthenticationFilter,
|
||||||
|
MiniappTenantContextFilter miniappTenantContextFilter
|
||||||
) throws Exception {
|
) throws Exception {
|
||||||
http
|
http
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
@@ -108,6 +110,7 @@ public class SecurityConfig {
|
|||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
response.getWriter().write("{\"code\":\"UNAUTHORIZED\",\"message\":\"未登录或登录已失效\",\"data\":null}");
|
response.getWriter().write("{\"code\":\"UNAUTHORIZED\",\"message\":\"未登录或登录已失效\",\"data\":null}");
|
||||||
}))
|
}))
|
||||||
|
.addFilterBefore(miniappTenantContextFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
.cors(Customizer.withDefaults());
|
.cors(Customizer.withDefaults());
|
||||||
return http.build();
|
return http.build();
|
||||||
@@ -128,7 +131,10 @@ class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
return uri.startsWith("/api/open/") || "/api/v1/auth/login".equals(uri);
|
if (uri == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return uri.contains("/api/open/") || uri.endsWith("/api/v1/auth/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -32,9 +32,4 @@ public class GlobalExceptionHandler {
|
|||||||
public ApiResponse<Void> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException exception) {
|
public ApiResponse<Void> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException exception) {
|
||||||
return ApiResponse.fail("FILE_TOO_LARGE", "上传图片不能超过 5MB");
|
return ApiResponse.fail("FILE_TOO_LARGE", "上传图片不能超过 5MB");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
|
||||||
public ApiResponse<Void> handleException(Exception exception) {
|
|
||||||
return ApiResponse.fail("INTERNAL_SERVER_ERROR", exception.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,18 +343,21 @@ public class CardProfileService {
|
|||||||
Map<Long, List<String>> specialtyMap = loadSpecialtyMap(cards.stream().map(CardProfileDO::getId).toList());
|
Map<Long, List<String>> specialtyMap = loadSpecialtyMap(cards.stream().map(CardProfileDO::getId).toList());
|
||||||
return cards.stream()
|
return cards.stream()
|
||||||
.filter(card -> {
|
.filter(card -> {
|
||||||
String deptName = deptMap.containsKey(card.getDeptId()) ? deptMap.get(card.getDeptId()).getDeptName() : "";
|
OrgDepartmentDO department = card.getDeptId() == null ? null : deptMap.get(card.getDeptId());
|
||||||
|
String deptName = department == null ? "" : department.getDeptName();
|
||||||
List<String> specialties = specialtyMap.getOrDefault(card.getId(), List.of());
|
List<String> specialties = specialtyMap.getOrDefault(card.getId(), List.of());
|
||||||
boolean keywordMatched = !StringUtils.hasText(keyword)
|
boolean keywordMatched = !StringUtils.hasText(keyword)
|
||||||
|| card.getCardName().contains(keyword)
|
|| containsText(card.getCardName(), keyword)
|
||||||
|| deptName.contains(keyword)
|
|| containsText(deptName, keyword)
|
||||||
|| specialties.stream().anyMatch(item -> item.contains(keyword));
|
|| specialties.stream().anyMatch(item -> containsText(item, keyword));
|
||||||
boolean officeMatched = !StringUtils.hasText(office) || office.equals(deptName);
|
boolean officeMatched = !StringUtils.hasText(office) || office.equals(deptName);
|
||||||
boolean areaMatched = !StringUtils.hasText(practiceArea) || specialties.stream().anyMatch(item -> item.equals(practiceArea));
|
boolean areaMatched = !StringUtils.hasText(practiceArea)
|
||||||
|
|| specialties.stream().anyMatch(item -> equalsText(item, practiceArea));
|
||||||
return keywordMatched && officeMatched && areaMatched;
|
return keywordMatched && officeMatched && areaMatched;
|
||||||
})
|
})
|
||||||
.map(card -> {
|
.map(card -> {
|
||||||
String deptName = deptMap.containsKey(card.getDeptId()) ? deptMap.get(card.getDeptId()).getDeptName() : "";
|
OrgDepartmentDO department = card.getDeptId() == null ? null : deptMap.get(card.getDeptId());
|
||||||
|
String deptName = department == null ? "" : department.getDeptName();
|
||||||
return new OpenCardListItem(
|
return new OpenCardListItem(
|
||||||
card.getId(),
|
card.getId(),
|
||||||
card.getCardName(),
|
card.getCardName(),
|
||||||
@@ -496,6 +499,14 @@ public class CardProfileService {
|
|||||||
return AUTO_MANAGED_ROLE_CODE.equals(roleCode);
|
return AUTO_MANAGED_ROLE_CODE.equals(roleCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean containsText(String source, String keyword) {
|
||||||
|
return source != null && keyword != null && source.contains(keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean equalsText(String left, String right) {
|
||||||
|
return left != null && left.equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
private SysUserDO createHiddenLawyerUser(LoginUser loginUser, UpsertCardRequest request) {
|
private SysUserDO createHiddenLawyerUser(LoginUser loginUser, UpsertCardRequest request) {
|
||||||
SysRoleDO role = getRequiredTenantRole(loginUser.tenantId(), AUTO_MANAGED_ROLE_CODE);
|
SysRoleDO role = getRequiredTenantRole(loginUser.tenantId(), AUTO_MANAGED_ROLE_CODE);
|
||||||
SysUserDO user = new SysUserDO();
|
SysUserDO user = new SysUserDO();
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -58,6 +59,7 @@ public class TenantOrgService {
|
|||||||
profile.setCreatedBy(loginUser.userId());
|
profile.setCreatedBy(loginUser.userId());
|
||||||
profile.setDeleted(0);
|
profile.setDeleted(0);
|
||||||
}
|
}
|
||||||
|
CoordinateValue coordinates = parseCoordinates(request.hqAddress(), request.hqLatitude(), request.hqLongitude());
|
||||||
profile.setFirmName(request.firmName());
|
profile.setFirmName(request.firmName());
|
||||||
profile.setFirmShortName(request.firmShortName());
|
profile.setFirmShortName(request.firmShortName());
|
||||||
profile.setEnglishName(request.englishName());
|
profile.setEnglishName(request.englishName());
|
||||||
@@ -68,8 +70,8 @@ public class TenantOrgService {
|
|||||||
profile.setWebsiteUrl(request.websiteUrl());
|
profile.setWebsiteUrl(request.websiteUrl());
|
||||||
profile.setWechatOfficialAccount(request.wechatOfficialAccount());
|
profile.setWechatOfficialAccount(request.wechatOfficialAccount());
|
||||||
profile.setHqAddress(request.hqAddress());
|
profile.setHqAddress(request.hqAddress());
|
||||||
profile.setHqLatitude(toBigDecimal(request.hqLatitude()));
|
profile.setHqLatitude(coordinates.latitude());
|
||||||
profile.setHqLongitude(toBigDecimal(request.hqLongitude()));
|
profile.setHqLongitude(coordinates.longitude());
|
||||||
profile.setUpdatedBy(loginUser.userId());
|
profile.setUpdatedBy(loginUser.userId());
|
||||||
if (profile.getId() == null) {
|
if (profile.getId() == null) {
|
||||||
orgFirmProfileMapper.insert(profile);
|
orgFirmProfileMapper.insert(profile);
|
||||||
@@ -217,11 +219,51 @@ public class TenantOrgService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal toBigDecimal(String value) {
|
private CoordinateValue parseCoordinates(String address, String latitude, String longitude) {
|
||||||
if (value == null || value.isBlank()) {
|
String latitudeText = normalizeCoordinate(latitude);
|
||||||
return null;
|
String longitudeText = normalizeCoordinate(longitude);
|
||||||
|
boolean hasLatitude = StringUtils.hasText(latitudeText);
|
||||||
|
boolean hasLongitude = StringUtils.hasText(longitudeText);
|
||||||
|
|
||||||
|
if (!hasLatitude && !hasLongitude) {
|
||||||
|
return new CoordinateValue(null, null);
|
||||||
}
|
}
|
||||||
return new BigDecimal(value);
|
if (!hasLatitude || !hasLongitude) {
|
||||||
|
throw new BusinessException("FIRM_COORDINATE_INVALID", "纬度和经度需同时填写");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(address)) {
|
||||||
|
throw new BusinessException("FIRM_ADDRESS_REQUIRED", "填写地图坐标时请同时填写详细地址");
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal latitudeValue = parseCoordinateValue(latitudeText, "纬度");
|
||||||
|
BigDecimal longitudeValue = parseCoordinateValue(longitudeText, "经度");
|
||||||
|
|
||||||
|
if (!isInRange(latitudeValue, -90, 90)) {
|
||||||
|
if (isInRange(latitudeValue, -180, 180) && isInRange(longitudeValue, -90, 90)) {
|
||||||
|
throw new BusinessException("FIRM_COORDINATE_INVALID", "纬度超出范围,请检查是否与经度填反");
|
||||||
|
}
|
||||||
|
throw new BusinessException("FIRM_COORDINATE_INVALID", "纬度范围应在 -90 到 90 之间");
|
||||||
|
}
|
||||||
|
if (!isInRange(longitudeValue, -180, 180)) {
|
||||||
|
throw new BusinessException("FIRM_COORDINATE_INVALID", "经度范围应在 -180 到 180 之间");
|
||||||
|
}
|
||||||
|
return new CoordinateValue(latitudeValue, longitudeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeCoordinate(String value) {
|
||||||
|
return value == null ? "" : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal parseCoordinateValue(String value, String fieldLabel) {
|
||||||
|
try {
|
||||||
|
return new BigDecimal(value);
|
||||||
|
} catch (NumberFormatException exception) {
|
||||||
|
throw new BusinessException("FIRM_COORDINATE_INVALID", fieldLabel + "必须是数字");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInRange(BigDecimal value, int min, int max) {
|
||||||
|
return value.compareTo(BigDecimal.valueOf(min)) >= 0 && value.compareTo(BigDecimal.valueOf(max)) <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveAssetUrl(Long assetId) {
|
private String resolveAssetUrl(Long assetId) {
|
||||||
@@ -270,6 +312,9 @@ public class TenantOrgService {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record CoordinateValue(BigDecimal latitude, BigDecimal longitude) {
|
||||||
|
}
|
||||||
|
|
||||||
public record PracticeAreaView(Long id, String areaCode, String areaName, Integer displayOrder, String areaStatus) {
|
public record PracticeAreaView(Long id, String areaCode, String areaName, Integer displayOrder, String areaStatus) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ public class MiniappTenantContextFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||||
return !request.getRequestURI().startsWith("/api/open/");
|
String uri = request.getRequestURI();
|
||||||
|
return uri == null || !uri.contains("/api/open/");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, reactive, ref } from 'vue'
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import type { UploadRequestOptions } from 'element-plus'
|
import type { UploadRequestOptions } from 'element-plus'
|
||||||
import { Plus } from '@element-plus/icons-vue'
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
@@ -19,6 +20,7 @@ function generateDeptCode() {
|
|||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
const imageUploading = reactive({
|
const imageUploading = reactive({
|
||||||
logo: false,
|
logo: false,
|
||||||
hero: false,
|
hero: false,
|
||||||
@@ -72,6 +74,95 @@ const deptOptions = computed(() => departments.value.map(item => ({
|
|||||||
value: item.id,
|
value: item.id,
|
||||||
})))
|
})))
|
||||||
|
|
||||||
|
function normalizeText(value: string | null | undefined) {
|
||||||
|
return typeof value === 'string' ? value.trim() : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCoordinate(value: string | null | undefined) {
|
||||||
|
const normalized = normalizeText(value)
|
||||||
|
if (!normalized) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const parsed = Number(normalized)
|
||||||
|
return Number.isFinite(parsed) ? parsed : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasCoordinateInput() {
|
||||||
|
return Boolean(normalizeText(profile.hqLatitude) || normalizeText(profile.hqLongitude))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCoordinateValidationMessage() {
|
||||||
|
const latitudeText = normalizeText(profile.hqLatitude)
|
||||||
|
const longitudeText = normalizeText(profile.hqLongitude)
|
||||||
|
const hasLatitude = Boolean(latitudeText)
|
||||||
|
const hasLongitude = Boolean(longitudeText)
|
||||||
|
|
||||||
|
if (!hasLatitude && !hasLongitude) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasLatitude || !hasLongitude) {
|
||||||
|
return '纬度和经度需同时填写'
|
||||||
|
}
|
||||||
|
|
||||||
|
const latitude = parseCoordinate(profile.hqLatitude)
|
||||||
|
if (latitude === null) {
|
||||||
|
return '纬度必须是数字'
|
||||||
|
}
|
||||||
|
|
||||||
|
const longitude = parseCoordinate(profile.hqLongitude)
|
||||||
|
if (longitude === null) {
|
||||||
|
return '经度必须是数字'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latitude < -90 || latitude > 90) {
|
||||||
|
if (latitude >= -180 && latitude <= 180 && longitude >= -90 && longitude <= 90) {
|
||||||
|
return '纬度超出范围,请检查是否与经度填反'
|
||||||
|
}
|
||||||
|
return '纬度范围应在 -90 到 90 之间'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (longitude < -180 || longitude > 180) {
|
||||||
|
return '经度范围应在 -180 到 180 之间'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!normalizeText(profile.hqAddress)) {
|
||||||
|
return '填写地图坐标时请同时填写详细地址'
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileRules: FormRules = {
|
||||||
|
firmName: [{ required: true, message: '请输入机构名称', trigger: 'blur' }],
|
||||||
|
hqAddress: [{
|
||||||
|
trigger: ['blur', 'change'],
|
||||||
|
validator: async () => {
|
||||||
|
if (hasCoordinateInput() && !normalizeText(profile.hqAddress)) {
|
||||||
|
throw new Error('填写地图坐标时请同时填写详细地址')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
hqLatitude: [{
|
||||||
|
trigger: ['blur', 'change'],
|
||||||
|
validator: async () => {
|
||||||
|
const message = getCoordinateValidationMessage()
|
||||||
|
if (message) {
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
hqLongitude: [{
|
||||||
|
trigger: ['blur', 'change'],
|
||||||
|
validator: async () => {
|
||||||
|
const message = getCoordinateValidationMessage()
|
||||||
|
if (message) {
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
@@ -135,6 +226,11 @@ function handleHeroUpload(options: UploadRequestOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveProfile() {
|
async function saveProfile() {
|
||||||
|
const valid = await formRef.value?.validate().then(() => true).catch(() => false)
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
const saved = await tenantApi.saveFirmProfile(profile)
|
const saved = await tenantApi.saveFirmProfile(profile)
|
||||||
@@ -267,9 +363,15 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-form label-position="top" class="card-form material-form">
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="profile"
|
||||||
|
:rules="profileRules"
|
||||||
|
label-position="top"
|
||||||
|
class="card-form material-form"
|
||||||
|
>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<el-form-item label="名称">
|
<el-form-item label="名称" prop="firmName">
|
||||||
<el-input v-model="profile.firmName" class="material-input" placeholder="完整的机构注册名称" />
|
<el-input v-model="profile.firmName" class="material-input" placeholder="完整的机构注册名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="简称">
|
<el-form-item label="简称">
|
||||||
@@ -284,13 +386,13 @@ onMounted(() => {
|
|||||||
<el-form-item label="官网地址">
|
<el-form-item label="官网地址">
|
||||||
<el-input v-model="profile.websiteUrl" class="material-input" />
|
<el-input v-model="profile.websiteUrl" class="material-input" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="详细地址" class="form-grid__full">
|
<el-form-item label="详细地址" prop="hqAddress" class="form-grid__full">
|
||||||
<el-input v-model="profile.hqAddress" class="material-input" placeholder="完整的真实办公地址" />
|
<el-input v-model="profile.hqAddress" class="material-input" placeholder="完整的真实办公地址" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="纬度 (LAT)">
|
<el-form-item label="纬度 (LAT)" prop="hqLatitude">
|
||||||
<el-input v-model="profile.hqLatitude" class="material-input" placeholder="例如:31.230416" />
|
<el-input v-model="profile.hqLatitude" class="material-input" placeholder="例如:31.230416" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="经度 (LNG)">
|
<el-form-item label="经度 (LNG)" prop="hqLongitude">
|
||||||
<el-input v-model="profile.hqLongitude" class="material-input" placeholder="例如:121.473701" />
|
<el-input v-model="profile.hqLongitude" class="material-input" placeholder="例如:121.473701" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="机构简介" class="form-grid__full">
|
<el-form-item label="机构简介" class="form-grid__full">
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ interface OpenFirmResponse {
|
|||||||
intro: string;
|
intro: string;
|
||||||
hotlinePhone: string;
|
hotlinePhone: string;
|
||||||
hqAddress: string;
|
hqAddress: string;
|
||||||
hqLatitude: number;
|
hqLatitude: number | string | null;
|
||||||
hqLongitude: number;
|
hqLongitude: number | string | null;
|
||||||
officeCount: number;
|
officeCount: number;
|
||||||
lawyerCount: number;
|
lawyerCount: number;
|
||||||
officeList: string[];
|
officeList: string[];
|
||||||
@@ -42,11 +42,62 @@ interface OpenCardDetailResponse {
|
|||||||
specialties: string[];
|
specialties: string[];
|
||||||
firmName: string;
|
firmName: string;
|
||||||
firmAddress: string;
|
firmAddress: string;
|
||||||
firmLatitude: number;
|
firmLatitude: number | string | null;
|
||||||
firmLongitude: number;
|
firmLongitude: number | string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCoordinate(value: number | string | null | undefined): number | null {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return Number.isFinite(value) ? value : null;
|
||||||
|
}
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const normalized = value.trim();
|
||||||
|
if (!normalized) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const parsed = Number(normalized);
|
||||||
|
return Number.isFinite(parsed) ? parsed : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLatitude(value: number | null): value is number {
|
||||||
|
return value !== null && value >= -90 && value <= 90;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLongitude(value: number | null): value is number {
|
||||||
|
return value !== null && value >= -180 && value <= 180;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCoordinates(latitude: number | string | null | undefined, longitude: number | string | null | undefined): {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
} {
|
||||||
|
const parsedLatitude = parseCoordinate(latitude);
|
||||||
|
const parsedLongitude = parseCoordinate(longitude);
|
||||||
|
|
||||||
|
if (isLatitude(parsedLatitude) && isLongitude(parsedLongitude)) {
|
||||||
|
return {
|
||||||
|
latitude: parsedLatitude,
|
||||||
|
longitude: parsedLongitude,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLatitude(parsedLongitude) && isLongitude(parsedLatitude)) {
|
||||||
|
return {
|
||||||
|
latitude: parsedLongitude,
|
||||||
|
longitude: parsedLatitude,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function toFirmInfo(payload: OpenFirmResponse): FirmInfo {
|
function toFirmInfo(payload: OpenFirmResponse): FirmInfo {
|
||||||
|
const coordinates = normalizeCoordinates(payload.hqLatitude, payload.hqLongitude);
|
||||||
return {
|
return {
|
||||||
id: payload.name || 'firm',
|
id: payload.name || 'firm',
|
||||||
name: payload.name || '',
|
name: payload.name || '',
|
||||||
@@ -54,8 +105,8 @@ function toFirmInfo(payload: OpenFirmResponse): FirmInfo {
|
|||||||
intro: payload.intro || '',
|
intro: payload.intro || '',
|
||||||
hotlinePhone: payload.hotlinePhone || '',
|
hotlinePhone: payload.hotlinePhone || '',
|
||||||
hqAddress: payload.hqAddress || '',
|
hqAddress: payload.hqAddress || '',
|
||||||
hqLatitude: payload.hqLatitude || 0,
|
hqLatitude: coordinates.latitude,
|
||||||
hqLongitude: payload.hqLongitude || 0,
|
hqLongitude: coordinates.longitude,
|
||||||
officeCount: payload.officeCount || 0,
|
officeCount: payload.officeCount || 0,
|
||||||
lawyerCount: payload.lawyerCount || 0,
|
lawyerCount: payload.lawyerCount || 0,
|
||||||
heroImage: payload.heroImage || '',
|
heroImage: payload.heroImage || '',
|
||||||
@@ -114,6 +165,7 @@ export async function getLawyerDetail(cardId: string, sourceType = 'DIRECT', sha
|
|||||||
const payload = await request<OpenCardDetailResponse>({
|
const payload = await request<OpenCardDetailResponse>({
|
||||||
url: `/api/open/cards/${encodeURIComponent(cardId)}?${queryParts.join('&')}`,
|
url: `/api/open/cards/${encodeURIComponent(cardId)}?${queryParts.join('&')}`,
|
||||||
});
|
});
|
||||||
|
const coordinates = normalizeCoordinates(payload.firmLatitude, payload.firmLongitude);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
firm: {
|
firm: {
|
||||||
@@ -123,8 +175,8 @@ export async function getLawyerDetail(cardId: string, sourceType = 'DIRECT', sha
|
|||||||
intro: '',
|
intro: '',
|
||||||
hotlinePhone: '',
|
hotlinePhone: '',
|
||||||
hqAddress: payload.firmAddress || '',
|
hqAddress: payload.firmAddress || '',
|
||||||
hqLatitude: payload.firmLatitude || 0,
|
hqLatitude: coordinates.latitude,
|
||||||
hqLongitude: payload.firmLongitude || 0,
|
hqLongitude: coordinates.longitude,
|
||||||
officeCount: 0,
|
officeCount: 0,
|
||||||
lawyerCount: 0,
|
lawyerCount: 0,
|
||||||
heroImage: '',
|
heroImage: '',
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export interface TenantRuntimeConfig {
|
|||||||
// develop 环境允许本地联调;trial/release 请替换为已备案且已配置到小程序后台“服务器域名”的 HTTPS 域名。
|
// develop 环境允许本地联调;trial/release 请替换为已备案且已配置到小程序后台“服务器域名”的 HTTPS 域名。
|
||||||
export const tenantRuntimeConfig: TenantRuntimeConfig = {
|
export const tenantRuntimeConfig: TenantRuntimeConfig = {
|
||||||
apiBaseUrlByEnv: {
|
apiBaseUrlByEnv: {
|
||||||
develop: 'http://127.0.0.1:8112',
|
develop: 'https://easyflowtech.cn/card',
|
||||||
trial: 'https://trial-api.example.com',
|
trial: 'https://easyflowtech.cn/card',
|
||||||
release: 'https://api.example.com',
|
release: 'https://easyflowtech.cn/card',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user