feat: 1. 添加企查查的按钮。新增相关组件和对接接口进行显示。
2. 为51707端口添加只存在交叉评查入口的项目启动配置。入口页添加相关的区分。 3. 完善文档列表的权限功能控制。 4. 隐藏系统概览中高风险用户的统计模块。 fix: 1. 修复合同起草无权访问却生成了新的模板文件的问题。 2. 修复文档类型无法编辑入口模块的问题。
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
# JWT Secret - 用于签名和验证前端JWT token
|
# JWT Secret - 用于签名和验证前端JWT token
|
||||||
# 生产环境请务必修改为强随机字符串(至少32个字符)
|
# 生产环境请务必修改为强随机字符串(至少32个字符)
|
||||||
JWT_SECRET=gdyc-super-secrets-jjwtt-key-change-this-in-production-20250721-from-login-callback
|
JWT_SECRET=gdyc-super-secrets-jjwtt-key-change-this-in-production-20250721-from-login-callback
|
||||||
|
|
||||||
|
# 交叉评查专属模式
|
||||||
|
# 设置为 true 时,端口51707只显示交叉评查入口,隐藏其他模块
|
||||||
|
# 设置为 false 时,保持正常模式显示所有模块
|
||||||
|
CROSS_CHECKING_ONLY_MODE=false
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
/**
|
||||||
|
* 服务端路由权限检查工具
|
||||||
|
* 用于在 action 中检查用户是否有权限访问目标路由
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getUserRoutesByRole, type MenuItem } from './user-routes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 MenuItem 数组中提取所有路径(包括子路由)
|
||||||
|
*/
|
||||||
|
function extractAllPaths(menuItems: MenuItem[]): string[] {
|
||||||
|
const paths: string[] = [];
|
||||||
|
|
||||||
|
function traverse(items: MenuItem[]) {
|
||||||
|
for (const item of items) {
|
||||||
|
paths.push(item.path);
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
traverse(item.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traverse(menuItems);
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查路径段是否看起来像动态ID
|
||||||
|
*/
|
||||||
|
function isDynamicIdSegment(segment: string): boolean {
|
||||||
|
// 纯数字
|
||||||
|
if (/^\d+$/.test(segment)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UUID格式
|
||||||
|
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包含数字的混合ID
|
||||||
|
if (/\d/.test(segment) && !/^[a-z]+(-[a-z]+)*$/i.test(segment)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查路径是否在允许列表中
|
||||||
|
*/
|
||||||
|
function isPathAllowed(pathname: string, allowedPaths: string[]): boolean {
|
||||||
|
// 精确匹配
|
||||||
|
if (allowedPaths.includes(pathname)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态路由匹配
|
||||||
|
for (const allowedPath of allowedPaths) {
|
||||||
|
if (pathname.startsWith(allowedPath + '/')) {
|
||||||
|
const subPath = pathname.substring(allowedPath.length + 1);
|
||||||
|
const segments = subPath.split('/');
|
||||||
|
const firstSegment = segments[0];
|
||||||
|
|
||||||
|
if (isDynamicIdSegment(firstSegment)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根路径
|
||||||
|
if (pathname === '/') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CheckRoutePermissionResult {
|
||||||
|
/** 是否有权限访问 */
|
||||||
|
allowed: boolean;
|
||||||
|
/** 错误信息(当 allowed 为 false 时) */
|
||||||
|
error?: string;
|
||||||
|
/** 用户允许访问的所有路由 */
|
||||||
|
allowedPaths?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查用户是否有权限访问指定路由
|
||||||
|
*
|
||||||
|
* @param targetPath 目标路由路径(如 '/contract-draft/1')
|
||||||
|
* @param userRole 用户角色
|
||||||
|
* @param jwt JWT token
|
||||||
|
* @returns 权限检查结果
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // 在 action 中使用
|
||||||
|
* export async function action({ request }: ActionFunctionArgs) {
|
||||||
|
* const { userInfo, frontendJWT } = await getUserSession(request);
|
||||||
|
*
|
||||||
|
* // 检查用户是否有权限访问目标路由
|
||||||
|
* const permissionCheck = await checkRoutePermission(
|
||||||
|
* '/contract-draft/1',
|
||||||
|
* userInfo.role,
|
||||||
|
* frontendJWT
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* if (!permissionCheck.allowed) {
|
||||||
|
* return Response.json({ error: permissionCheck.error }, { status: 403 });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // 继续执行 action 逻辑...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export async function checkRoutePermission(
|
||||||
|
targetPath: string,
|
||||||
|
userRole: string,
|
||||||
|
jwt?: string
|
||||||
|
): Promise<CheckRoutePermissionResult> {
|
||||||
|
if (!jwt) {
|
||||||
|
return {
|
||||||
|
allowed: false,
|
||||||
|
error: '未提供认证信息'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取用户的路由权限(包含隐藏路由,用于权限校验)
|
||||||
|
const routesResult = await getUserRoutesByRole(userRole, jwt, true);
|
||||||
|
|
||||||
|
if (!routesResult.success || !routesResult.data) {
|
||||||
|
return {
|
||||||
|
allowed: false,
|
||||||
|
error: routesResult.error || '获取用户权限失败'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取所有允许的路径
|
||||||
|
const allowedPaths = extractAllPaths(routesResult.data);
|
||||||
|
|
||||||
|
// 检查目标路径是否在允许列表中
|
||||||
|
const allowed = isPathAllowed(targetPath, allowedPaths);
|
||||||
|
|
||||||
|
if (!allowed) {
|
||||||
|
console.warn(`[checkRoutePermission] 用户无权访问路由: ${targetPath}`);
|
||||||
|
console.warn(`[checkRoutePermission] 用户允许的路由: ${allowedPaths.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
allowed,
|
||||||
|
error: allowed ? undefined : '您没有权限访问目标页面',
|
||||||
|
allowedPaths
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[checkRoutePermission] 权限检查失败:', error);
|
||||||
|
return {
|
||||||
|
allowed: false,
|
||||||
|
error: '权限检查失败,请稍后重试'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简化版:直接检查是否有权限,抛出 403 Response
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* export async function action({ request }: ActionFunctionArgs) {
|
||||||
|
* const { userInfo, frontendJWT } = await getUserSession(request);
|
||||||
|
*
|
||||||
|
* // 如果没有权限,会自动抛出 403 Response
|
||||||
|
* await requireRoutePermission('/contract-draft/1', userInfo.role, frontendJWT);
|
||||||
|
*
|
||||||
|
* // 继续执行 action 逻辑...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export async function requireRoutePermission(
|
||||||
|
targetPath: string,
|
||||||
|
userRole: string,
|
||||||
|
jwt?: string
|
||||||
|
): Promise<void> {
|
||||||
|
const result = await checkRoutePermission(targetPath, userRole, jwt);
|
||||||
|
|
||||||
|
if (!result.allowed) {
|
||||||
|
throw new Response(result.error || '无权访问', { status: 403 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
/**
|
||||||
|
* 企查查 API 接口封装
|
||||||
|
* 提供企业工商信息和失信核查查询功能
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { post } from '../axios-client';
|
||||||
|
import type {
|
||||||
|
BusinessInfoResult,
|
||||||
|
DishonestyResult,
|
||||||
|
Paging,
|
||||||
|
} from '~/components/corporate-information/types';
|
||||||
|
|
||||||
|
// ==================== 请求/响应类型定义 ====================
|
||||||
|
|
||||||
|
/** 查询企业信息请求参数 */
|
||||||
|
export interface QueryCompanyRequest {
|
||||||
|
/** 查询关键词(企业名称或统一社会信用代码),2-200字符 */
|
||||||
|
keyword: string;
|
||||||
|
/** 是否强制刷新,默认false */
|
||||||
|
force_refresh?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询企业信息响应数据 */
|
||||||
|
export interface QueryCompanyData {
|
||||||
|
/** 查询关键词 */
|
||||||
|
search_key: string;
|
||||||
|
/** 统一社会信用代码 */
|
||||||
|
credit_code: string;
|
||||||
|
/** 企业名称 */
|
||||||
|
company_name: string;
|
||||||
|
/** 企业工商信息(410接口原始返回) */
|
||||||
|
enterprise: BusinessInfoResult | null;
|
||||||
|
/** 失信核查信息(740接口原始返回) */
|
||||||
|
dishonesty: {
|
||||||
|
/** 核查结果:0-无失信记录,1-有失信记录 */
|
||||||
|
VerifyResult: number;
|
||||||
|
/** 失信记录列表 */
|
||||||
|
Data: DishonestyResult['Data'] | null;
|
||||||
|
} | null;
|
||||||
|
/** 是否有失信记录 */
|
||||||
|
has_dishonesty: boolean;
|
||||||
|
/** 失信记录数量 */
|
||||||
|
dishonesty_count: number;
|
||||||
|
/** 数据更新时间 */
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询企业信息响应 */
|
||||||
|
export interface QueryCompanyResponse {
|
||||||
|
/** 查询是否成功 */
|
||||||
|
success: boolean;
|
||||||
|
/** 响应消息 */
|
||||||
|
message: string;
|
||||||
|
/** 企业信息 */
|
||||||
|
data: QueryCompanyData | null;
|
||||||
|
/** 错误码(失败时返回) */
|
||||||
|
error_code?: string;
|
||||||
|
/** 错误详情(失败时返回) */
|
||||||
|
error_details?: {
|
||||||
|
order_number: string;
|
||||||
|
is_charged: boolean;
|
||||||
|
is_valid_request: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Mock 数据 ====================
|
||||||
|
|
||||||
|
/** Mock 企业工商信息(腾讯科技示例) */
|
||||||
|
const MOCK_ENTERPRISE_DATA: BusinessInfoResult = {
|
||||||
|
KeyNo: 'p5ef6e1c39f4817d33dfb4e9',
|
||||||
|
Name: '腾讯科技(深圳)有限公司',
|
||||||
|
No: '440301103206221',
|
||||||
|
BelongOrg: '深圳市市场监督管理局',
|
||||||
|
OperId: 'p5ef6e1c39f4817d33dfb4e9op',
|
||||||
|
OperName: '马化腾',
|
||||||
|
DesignatedRepresentativeList: [],
|
||||||
|
StartDate: '2000-02-24 00:00:00',
|
||||||
|
EndDate: '',
|
||||||
|
Status: '存续',
|
||||||
|
Province: 'GD',
|
||||||
|
UpdatedDate: '2024-12-10 15:30:00',
|
||||||
|
CreditCode: '91440300708461136T',
|
||||||
|
RegistCapi: '65000万人民币',
|
||||||
|
RegisteredCapital: '65000',
|
||||||
|
RegisteredCapitalUnit: '万',
|
||||||
|
RegisteredCapitalCCY: 'CNY',
|
||||||
|
EconKind: '有限责任公司',
|
||||||
|
Address: '深圳市南山区粤海街道科技中一路腾讯大厦35层',
|
||||||
|
Scope: '从事计算机软硬件及周边设备、通讯设备、数码产品、网络设备的技术开发、销售及相关的技术咨询、技术服务;经营进出口业务;从事广告业务;物业管理',
|
||||||
|
TermStart: '2000-02-24 00:00:00',
|
||||||
|
TermEnd: '2050-02-24 00:00:00',
|
||||||
|
CheckDate: '2023-08-15 00:00:00',
|
||||||
|
OrgNo: '708461136',
|
||||||
|
IsOnStock: '1',
|
||||||
|
StockNumber: '00700.HK',
|
||||||
|
StockType: '港股',
|
||||||
|
OriginalName: [
|
||||||
|
{
|
||||||
|
Name: '深圳市腾讯计算机系统有限公司',
|
||||||
|
ChangeDate: '2000-06-01',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ImageUrl: 'https://img.qichacha.com/xxx.png',
|
||||||
|
EntType: '1',
|
||||||
|
RecCap: '65000万人民币',
|
||||||
|
PaidUpCapital: '65000',
|
||||||
|
PaidUpCapitalUnit: '万',
|
||||||
|
PaidUpCapitalCCY: 'CNY',
|
||||||
|
RevokeInfo: {
|
||||||
|
CancelDate: '',
|
||||||
|
CancelReason: '',
|
||||||
|
RevokeDate: '',
|
||||||
|
RevokeReason: '',
|
||||||
|
},
|
||||||
|
Area: {
|
||||||
|
Province: '广东省',
|
||||||
|
City: '深圳市',
|
||||||
|
County: '南山区',
|
||||||
|
},
|
||||||
|
AreaCode: '440305',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Mock 失信记录(有失信记录的示例) */
|
||||||
|
const MOCK_DISHONESTY_WITH_RECORDS = {
|
||||||
|
VerifyResult: 1,
|
||||||
|
Data: [
|
||||||
|
{
|
||||||
|
Id: 'c2edc410f8ceed3ed256055baa8df1432',
|
||||||
|
Liandate: '2021-08-10',
|
||||||
|
Anno: '(2021)粤0106执34224号',
|
||||||
|
Executegov: '广州市天河区人民法院',
|
||||||
|
Executestatus: '全部未履行',
|
||||||
|
Publicdate: '2022-01-07',
|
||||||
|
Executeno: '(2020)粤0106民初12345号',
|
||||||
|
ActionRemark: '有履行能力而拒不履行生效法律文书确定义务',
|
||||||
|
Amount: '1098000',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Mock 无失信记录 */
|
||||||
|
const MOCK_DISHONESTY_NO_RECORDS = {
|
||||||
|
VerifyResult: 0,
|
||||||
|
Data: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 是否使用 Mock 数据 */
|
||||||
|
const USE_MOCK = true;
|
||||||
|
|
||||||
|
// ==================== API 方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询企业信息(工商信息 + 失信核查)
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns 企业完整信息
|
||||||
|
*/
|
||||||
|
export async function queryCompanyInfo(
|
||||||
|
params: QueryCompanyRequest
|
||||||
|
): Promise<QueryCompanyResponse> {
|
||||||
|
// 使用 Mock 数据
|
||||||
|
if (USE_MOCK) {
|
||||||
|
// 模拟网络延迟
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 800));
|
||||||
|
|
||||||
|
// 根据关键词返回不同的 Mock 数据
|
||||||
|
const hasDishonesty = params.keyword.includes('失信') || params.keyword.includes('建设');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: '查询成功',
|
||||||
|
data: {
|
||||||
|
search_key: params.keyword,
|
||||||
|
credit_code: MOCK_ENTERPRISE_DATA.CreditCode,
|
||||||
|
company_name: MOCK_ENTERPRISE_DATA.Name,
|
||||||
|
enterprise: MOCK_ENTERPRISE_DATA,
|
||||||
|
dishonesty: hasDishonesty ? MOCK_DISHONESTY_WITH_RECORDS : MOCK_DISHONESTY_NO_RECORDS,
|
||||||
|
has_dishonesty: hasDishonesty,
|
||||||
|
dishonesty_count: hasDishonesty ? 1 : 0,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 真实 API 调用
|
||||||
|
const response = await post<QueryCompanyResponse>('/api/v2/qichacha/company', params);
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: response.error,
|
||||||
|
data: null,
|
||||||
|
error_code: String(response.status),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data as QueryCompanyResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅查询工商信息
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns 企业工商信息
|
||||||
|
*/
|
||||||
|
export async function queryEnterpriseInfo(
|
||||||
|
params: QueryCompanyRequest
|
||||||
|
): Promise<QueryCompanyResponse> {
|
||||||
|
if (USE_MOCK) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: '查询成功',
|
||||||
|
data: {
|
||||||
|
search_key: params.keyword,
|
||||||
|
credit_code: MOCK_ENTERPRISE_DATA.CreditCode,
|
||||||
|
company_name: MOCK_ENTERPRISE_DATA.Name,
|
||||||
|
enterprise: MOCK_ENTERPRISE_DATA,
|
||||||
|
dishonesty: null,
|
||||||
|
has_dishonesty: false,
|
||||||
|
dishonesty_count: 0,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await post<QueryCompanyResponse>('/api/v2/qichacha/enterprise', params);
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: response.error,
|
||||||
|
data: null,
|
||||||
|
error_code: String(response.status),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data as QueryCompanyResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅查询失信信息
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns 失信核查信息
|
||||||
|
*/
|
||||||
|
export async function queryDishonestyInfo(
|
||||||
|
params: QueryCompanyRequest
|
||||||
|
): Promise<QueryCompanyResponse> {
|
||||||
|
if (USE_MOCK) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
const hasDishonesty = params.keyword.includes('失信') || params.keyword.includes('建设');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: '查询成功',
|
||||||
|
data: {
|
||||||
|
search_key: params.keyword,
|
||||||
|
credit_code: '',
|
||||||
|
company_name: params.keyword,
|
||||||
|
enterprise: null,
|
||||||
|
dishonesty: hasDishonesty ? MOCK_DISHONESTY_WITH_RECORDS : MOCK_DISHONESTY_NO_RECORDS,
|
||||||
|
has_dishonesty: hasDishonesty,
|
||||||
|
dishonesty_count: hasDishonesty ? 1 : 0,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await post<QueryCompanyResponse>('/api/v2/qichacha/dishonesty', params);
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: response.error,
|
||||||
|
data: null,
|
||||||
|
error_code: String(response.status),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data as QueryCompanyResponse;
|
||||||
|
}
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
/**
|
||||||
|
* 企业工商信息展示组件
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BusinessInfoProps } from './types';
|
||||||
|
import { EntTypeLabel } from './types';
|
||||||
|
|
||||||
|
/** 格式化日期(移除时间部分) */
|
||||||
|
function formatDate(dateStr: string | null | undefined): string {
|
||||||
|
if (!dateStr) return '-';
|
||||||
|
// 移除时间部分,只保留日期
|
||||||
|
return dateStr.split(' ')[0] || dateStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 格式化金额 */
|
||||||
|
function formatAmount(amount: string | null | undefined, unit?: string, ccy?: string): string {
|
||||||
|
if (!amount) return '-';
|
||||||
|
const currencyMap: Record<string, string> = {
|
||||||
|
'CNY': '人民币',
|
||||||
|
'USD': '美元',
|
||||||
|
'HKD': '港币',
|
||||||
|
'EUR': '欧元',
|
||||||
|
};
|
||||||
|
const currencyLabel = ccy ? currencyMap[ccy] || ccy : '';
|
||||||
|
return `${amount}${unit || ''}${currencyLabel ? ` (${currencyLabel})` : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BusinessInfo({ data, loading, error }: BusinessInfoProps) {
|
||||||
|
// 加载中状态
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg shadow-sm p-6">
|
||||||
|
<div className="flex items-center justify-center py-12">
|
||||||
|
<i className="ri-loader-4-line animate-spin text-2xl text-gray-400 mr-2"></i>
|
||||||
|
<span className="text-gray-500">加载企业信息中...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误状态
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg shadow-sm p-6">
|
||||||
|
<div className="flex items-center justify-center py-12 text-red-500">
|
||||||
|
<i className="ri-error-warning-line text-2xl mr-2"></i>
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无数据状态
|
||||||
|
if (!data) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg shadow-sm p-6">
|
||||||
|
<div className="flex items-center justify-center py-12 text-gray-400">
|
||||||
|
<i className="ri-building-line text-2xl mr-2"></i>
|
||||||
|
<span>暂无企业工商信息</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否有注销/吊销信息
|
||||||
|
const hasRevokeInfo = data.RevokeInfo && (data.RevokeInfo.CancelDate || data.RevokeInfo.RevokeDate);
|
||||||
|
|
||||||
|
// 获取状态标签颜色
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
if (status === '存续' || status === '在业' || status === '开业') {
|
||||||
|
return 'bg-green-100 text-green-700 border-green-200';
|
||||||
|
}
|
||||||
|
if (status === '注销' || status === '吊销') {
|
||||||
|
return 'bg-red-100 text-red-700 border-red-200';
|
||||||
|
}
|
||||||
|
return 'bg-gray-100 text-gray-700 border-gray-200';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg shadow-sm">
|
||||||
|
{/* 标题栏 */}
|
||||||
|
<div className="px-6 py-4 border-b border-gray-100">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<i className="ri-building-2-line text-xl text-[#00684a] mr-2"></i>
|
||||||
|
<h3 className="text-lg font-medium text-gray-900">企业工商信息</h3>
|
||||||
|
</div>
|
||||||
|
{data.Status && (
|
||||||
|
<span className={`px-3 py-1 text-sm rounded-full border ${getStatusColor(data.Status)}`}>
|
||||||
|
{data.Status}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 企业头部信息 */}
|
||||||
|
<div className="px-6 py-4 bg-gray-50 border-b border-gray-100">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
{data.ImageUrl && (
|
||||||
|
<img
|
||||||
|
src={data.ImageUrl}
|
||||||
|
alt="企业Logo"
|
||||||
|
className="w-16 h-16 rounded-lg object-contain bg-white border border-gray-200"
|
||||||
|
onError={(e) => {
|
||||||
|
(e.target as HTMLImageElement).style.display = 'none';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h2 className="text-xl font-semibold text-gray-900 truncate">{data.Name}</h2>
|
||||||
|
<div className="mt-1 flex flex-wrap items-center gap-2 text-sm text-gray-500">
|
||||||
|
{data.CreditCode && (
|
||||||
|
<span className="bg-white px-2 py-0.5 rounded border border-gray-200">
|
||||||
|
统一社会信用代码:{data.CreditCode}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{data.EntType && (
|
||||||
|
<span className="bg-[#e6f4ff] text-[#0958d9] px-2 py-0.5 rounded border border-[#91caff]">
|
||||||
|
{EntTypeLabel[data.EntType] || '其他'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{data.IsOnStock === '1' && (
|
||||||
|
<span className="bg-orange-100 text-orange-700 px-2 py-0.5 rounded border border-orange-200">
|
||||||
|
{data.StockType}:{data.StockNumber}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* 曾用名 */}
|
||||||
|
{data.OriginalName && data.OriginalName.length > 0 && (
|
||||||
|
<div className="mt-2 text-sm text-gray-500">
|
||||||
|
<span className="text-gray-400">曾用名:</span>
|
||||||
|
{data.OriginalName.map((item, index) => (
|
||||||
|
<span key={index} className="mr-2">
|
||||||
|
{item.Name}
|
||||||
|
{item.ChangeDate && <span className="text-gray-400 text-xs ml-1">({formatDate(item.ChangeDate)})</span>}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 详细信息 */}
|
||||||
|
<div className="px-6 py-4">
|
||||||
|
{/* 基本信息 */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h4 className="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
||||||
|
<i className="ri-information-line mr-1 text-gray-400"></i>
|
||||||
|
基本信息
|
||||||
|
</h4>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
<InfoItem label="法定代表人" value={data.OperName} />
|
||||||
|
<InfoItem label="企业类型" value={data.EconKind} />
|
||||||
|
<InfoItem label="成立日期" value={formatDate(data.StartDate)} />
|
||||||
|
<InfoItem label="核准日期" value={formatDate(data.CheckDate)} />
|
||||||
|
<InfoItem label="营业期限" value={`${formatDate(data.TermStart)} 至 ${formatDate(data.TermEnd)}`} />
|
||||||
|
<InfoItem label="登记机关" value={data.BelongOrg} />
|
||||||
|
<InfoItem label="工商注册号" value={data.No} />
|
||||||
|
<InfoItem label="组织机构代码" value={data.OrgNo} />
|
||||||
|
{data.Area && (
|
||||||
|
<InfoItem
|
||||||
|
label="所属地区"
|
||||||
|
value={`${data.Area.Province || ''}${data.Area.City || ''}${data.Area.County || ''}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 资本信息 */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h4 className="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
||||||
|
<i className="ri-money-cny-circle-line mr-1 text-gray-400"></i>
|
||||||
|
资本信息
|
||||||
|
</h4>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<InfoItem
|
||||||
|
label="注册资本"
|
||||||
|
value={data.RegistCapi || formatAmount(data.RegisteredCapital, data.RegisteredCapitalUnit, data.RegisteredCapitalCCY)}
|
||||||
|
/>
|
||||||
|
<InfoItem
|
||||||
|
label="实缴资本"
|
||||||
|
value={data.RecCap || formatAmount(data.PaidUpCapital, data.PaidUpCapitalUnit, data.PaidUpCapitalCCY)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 注册地址 */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h4 className="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
||||||
|
<i className="ri-map-pin-line mr-1 text-gray-400"></i>
|
||||||
|
注册地址
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-gray-600 bg-gray-50 p-3 rounded-lg">{data.Address || '-'}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 经营范围 */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h4 className="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
||||||
|
<i className="ri-file-list-3-line mr-1 text-gray-400"></i>
|
||||||
|
经营范围
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-gray-600 bg-gray-50 p-3 rounded-lg leading-relaxed">
|
||||||
|
{data.Scope || '-'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 委派代表 */}
|
||||||
|
{data.DesignatedRepresentativeList && data.DesignatedRepresentativeList.length > 0 && (
|
||||||
|
<div className="mb-6">
|
||||||
|
<h4 className="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
||||||
|
<i className="ri-user-star-line mr-1 text-gray-400"></i>
|
||||||
|
委派代表
|
||||||
|
</h4>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-gray-50">
|
||||||
|
<th className="px-4 py-2 text-left font-medium text-gray-600">合伙人名称</th>
|
||||||
|
<th className="px-4 py-2 text-left font-medium text-gray-600">委派代表</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.DesignatedRepresentativeList.map((rep, index) => (
|
||||||
|
<tr key={index} className="border-b border-gray-100">
|
||||||
|
<td className="px-4 py-2 text-gray-700">{rep.PartnerName || '-'}</td>
|
||||||
|
<td className="px-4 py-2 text-gray-700">{rep.DelegatedName || '-'}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 注销/吊销信息 */}
|
||||||
|
{hasRevokeInfo && (
|
||||||
|
<div className="mb-6">
|
||||||
|
<h4 className="text-sm font-medium text-red-600 mb-3 flex items-center">
|
||||||
|
<i className="ri-error-warning-line mr-1"></i>
|
||||||
|
注销/吊销信息
|
||||||
|
</h4>
|
||||||
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{data.RevokeInfo?.CancelDate && (
|
||||||
|
<>
|
||||||
|
<InfoItem label="注销日期" value={formatDate(data.RevokeInfo.CancelDate)} labelClassName="text-red-600" />
|
||||||
|
<InfoItem label="注销原因" value={data.RevokeInfo.CancelReason} labelClassName="text-red-600" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{data.RevokeInfo?.RevokeDate && (
|
||||||
|
<>
|
||||||
|
<InfoItem label="吊销日期" value={formatDate(data.RevokeInfo.RevokeDate)} labelClassName="text-red-600" />
|
||||||
|
<InfoItem label="吊销原因" value={data.RevokeInfo.RevokeReason} labelClassName="text-red-600" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 更新时间 */}
|
||||||
|
{data.UpdatedDate && (
|
||||||
|
<div className="text-xs text-gray-400 text-right">
|
||||||
|
数据更新时间:{formatDate(data.UpdatedDate)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 信息项组件 */
|
||||||
|
interface InfoItemProps {
|
||||||
|
label: string;
|
||||||
|
value: string | null | undefined;
|
||||||
|
labelClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function InfoItem({ label, value, labelClassName = 'text-gray-500' }: InfoItemProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className={`text-xs ${labelClassName}`}>{label}</span>
|
||||||
|
<span className="text-sm text-gray-800 mt-0.5">{value || '-'}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
/**
|
||||||
|
* 企业信息模态框组件
|
||||||
|
* 用于展示企业工商信息和失信核查信息
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { CorporateInformation } from './CorporateInformation';
|
||||||
|
import { toastService } from '../ui/Toast';
|
||||||
|
import { messageService } from '../ui/MessageModal';
|
||||||
|
import type { BusinessInfoResult, DishonestyResult, Paging } from './types';
|
||||||
|
|
||||||
|
export interface CorporateInfoModalProps {
|
||||||
|
/** 是否显示模态框 */
|
||||||
|
visible: boolean;
|
||||||
|
/** 关闭模态框回调 */
|
||||||
|
onClose: () => void;
|
||||||
|
/** 企业名称(用于标题显示) */
|
||||||
|
companyName?: string;
|
||||||
|
/** 企业工商信息 */
|
||||||
|
businessInfo: BusinessInfoResult | null;
|
||||||
|
/** 失信核查数据 */
|
||||||
|
dishonestyInfo: DishonestyResult | null;
|
||||||
|
/** 失信核查分页信息 */
|
||||||
|
dishonestyPaging?: Paging | null;
|
||||||
|
/** 企业工商信息加载中 */
|
||||||
|
businessLoading?: boolean;
|
||||||
|
/** 失信核查加载中 */
|
||||||
|
dishonestyLoading?: boolean;
|
||||||
|
/** 企业工商信息错误 */
|
||||||
|
businessError?: string | null;
|
||||||
|
/** 失信核查错误 */
|
||||||
|
dishonestyError?: string | null;
|
||||||
|
/** 数据更新时间(ISO格式) */
|
||||||
|
updatedAt?: string | null;
|
||||||
|
/** 强制刷新回调(对接企查查重新查询) */
|
||||||
|
onForceRefresh?: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化更新时间(UTC转北京时间)
|
||||||
|
* @param isoString ISO格式的时间字符串
|
||||||
|
* @returns 格式化后的北京时间字符串
|
||||||
|
*/
|
||||||
|
function formatUpdatedTime(isoString: string | null | undefined): string {
|
||||||
|
if (!isoString) return '-';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const date = new Date(isoString);
|
||||||
|
|
||||||
|
// 检查日期是否有效
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Intl.DateTimeFormat 格式化为北京时间
|
||||||
|
const formatter = new Intl.DateTimeFormat('zh-CN', {
|
||||||
|
timeZone: 'Asia/Shanghai',
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return formatter.format(date);
|
||||||
|
} catch {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CorporateInfoModal({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
companyName,
|
||||||
|
businessInfo,
|
||||||
|
dishonestyInfo,
|
||||||
|
dishonestyPaging,
|
||||||
|
businessLoading = false,
|
||||||
|
dishonestyLoading = false,
|
||||||
|
businessError,
|
||||||
|
dishonestyError,
|
||||||
|
updatedAt,
|
||||||
|
onForceRefresh,
|
||||||
|
}: CorporateInfoModalProps) {
|
||||||
|
// 强制刷新按钮加载状态
|
||||||
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
|
// ESC 键关闭模态框
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape' && visible) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[visible, onClose]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [handleKeyDown]);
|
||||||
|
|
||||||
|
// 阻止背景滚动
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
} else {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
};
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
|
// 处理强制刷新按钮点击
|
||||||
|
const handleForceRefreshClick = () => {
|
||||||
|
messageService.show({
|
||||||
|
title: '确认重新查询',
|
||||||
|
message: '该操作将对接企查查API进行实时查询,会产生费用(约0.50元/次)。确定要继续吗?',
|
||||||
|
type: 'warning',
|
||||||
|
confirmText: '确认查询',
|
||||||
|
cancelText: '取消',
|
||||||
|
onConfirm: async () => {
|
||||||
|
if (onForceRefresh) {
|
||||||
|
setRefreshing(true);
|
||||||
|
try {
|
||||||
|
await onForceRefresh();
|
||||||
|
toastService.success('已提交查询,请稍后再查看企业信息');
|
||||||
|
onClose();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('强制刷新失败:', error);
|
||||||
|
toastService.error('查询失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
setRefreshing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="corporate-info-modal-overlay"
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: 1000,
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
// 点击背景关闭
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="corporate-info-modal"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: '8px',
|
||||||
|
width: '90%',
|
||||||
|
maxWidth: '900px',
|
||||||
|
maxHeight: '85vh',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.15)',
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{/* 模态框头部 */}
|
||||||
|
<div
|
||||||
|
className="corporate-info-modal-header"
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: '16px 24px',
|
||||||
|
borderBottom: '1px solid #e5e7eb',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||||
|
<i
|
||||||
|
className="ri-building-line"
|
||||||
|
style={{ fontSize: '20px', color: '#00684a' }}
|
||||||
|
></i>
|
||||||
|
<h3
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
fontSize: '18px',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: '#1f2937',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
企业信息查询
|
||||||
|
</h3>
|
||||||
|
{companyName && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: '8px',
|
||||||
|
padding: '2px 8px',
|
||||||
|
backgroundColor: '#f3f4f6',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#6b7280',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{companyName}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
style={{
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: '4px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
borderRadius: '4px',
|
||||||
|
transition: 'background-color 0.2s',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = '#f3f4f6';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'transparent';
|
||||||
|
}}
|
||||||
|
title="关闭 (ESC)"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className="ri-close-line"
|
||||||
|
style={{ fontSize: '24px', color: '#6b7280' }}
|
||||||
|
></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 模态框内容 */}
|
||||||
|
<div
|
||||||
|
className="corporate-info-modal-body"
|
||||||
|
style={{
|
||||||
|
padding: '24px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CorporateInformation
|
||||||
|
businessInfo={businessInfo}
|
||||||
|
dishonestyInfo={dishonestyInfo}
|
||||||
|
dishonestyPaging={dishonestyPaging}
|
||||||
|
businessLoading={businessLoading}
|
||||||
|
dishonestyLoading={dishonestyLoading}
|
||||||
|
businessError={businessError}
|
||||||
|
dishonestyError={dishonestyError}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 模态框底部 */}
|
||||||
|
<div
|
||||||
|
className="corporate-info-modal-footer"
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '12px 24px',
|
||||||
|
borderTop: '1px solid #e5e7eb',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 左侧:最近查询时间 */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '6px',
|
||||||
|
fontSize: '13px',
|
||||||
|
color: '#6b7280',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="ri-time-line" style={{ fontSize: '14px' }}></i>
|
||||||
|
<span>最近查询时间:{formatUpdatedTime(updatedAt)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧:按钮组 */}
|
||||||
|
<div style={{ display: 'flex', gap: '12px' }}>
|
||||||
|
{/* 对接企查查重新查询按钮 */}
|
||||||
|
<button
|
||||||
|
onClick={handleForceRefreshClick}
|
||||||
|
disabled={refreshing || businessLoading || dishonestyLoading}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
backgroundColor: refreshing ? '#d1d5db' : '#00684a',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#ffffff',
|
||||||
|
cursor: refreshing || businessLoading || dishonestyLoading ? 'not-allowed' : 'pointer',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '6px',
|
||||||
|
opacity: refreshing || businessLoading || dishonestyLoading ? 0.6 : 1,
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
if (!refreshing && !businessLoading && !dishonestyLoading) {
|
||||||
|
e.currentTarget.style.backgroundColor = '#005a3f';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
if (!refreshing && !businessLoading && !dishonestyLoading) {
|
||||||
|
e.currentTarget.style.backgroundColor = '#00684a';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className={refreshing ? 'ri-loader-4-line' : 'ri-refresh-line'} style={{ fontSize: '14px' }}></i>
|
||||||
|
{refreshing ? '查询中...' : '对接企查查重新查询'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* 关闭按钮 */}
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
style={{
|
||||||
|
padding: '8px 24px',
|
||||||
|
backgroundColor: '#f3f4f6',
|
||||||
|
border: '1px solid #d1d5db',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#374151',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = '#e5e7eb';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = '#f3f4f6';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 企业综合信息展示组件
|
||||||
|
* 整合企业工商信息和失信核查信息
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BusinessInfo } from './BusinessInfo';
|
||||||
|
import { DishonestyInfo } from './DishonestyInfo';
|
||||||
|
import type { CorporateInformationProps } from './types';
|
||||||
|
|
||||||
|
export function CorporateInformation({
|
||||||
|
businessInfo,
|
||||||
|
dishonestyInfo,
|
||||||
|
dishonestyPaging,
|
||||||
|
businessLoading,
|
||||||
|
dishonestyLoading,
|
||||||
|
businessError,
|
||||||
|
dishonestyError,
|
||||||
|
}: CorporateInformationProps) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* 企业工商信息 */}
|
||||||
|
<BusinessInfo
|
||||||
|
data={businessInfo}
|
||||||
|
loading={businessLoading}
|
||||||
|
error={businessError}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 失信核查信息 */}
|
||||||
|
<DishonestyInfo
|
||||||
|
data={dishonestyInfo}
|
||||||
|
paging={dishonestyPaging}
|
||||||
|
loading={dishonestyLoading}
|
||||||
|
error={dishonestyError}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
/**
|
||||||
|
* 失信核查信息展示组件
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { DishonestyInfoProps, DishonestyRecord } from './types';
|
||||||
|
|
||||||
|
/** 格式化日期 */
|
||||||
|
function formatDate(dateStr: string | null | undefined): string {
|
||||||
|
if (!dateStr) return '-';
|
||||||
|
return dateStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 格式化金额(元转万元) */
|
||||||
|
function formatAmount(amountStr: string | null | undefined): string {
|
||||||
|
if (!amountStr) return '-';
|
||||||
|
const amount = parseFloat(amountStr);
|
||||||
|
if (isNaN(amount)) return amountStr;
|
||||||
|
if (amount >= 10000) {
|
||||||
|
return `${(amount / 10000).toFixed(2)}万元`;
|
||||||
|
}
|
||||||
|
return `${amount.toFixed(2)}元`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取履行情况标签颜色 */
|
||||||
|
function getExecuteStatusColor(status: string): string {
|
||||||
|
if (status === '全部履行') {
|
||||||
|
return 'bg-green-100 text-green-700 border-green-200';
|
||||||
|
}
|
||||||
|
if (status === '部分履行') {
|
||||||
|
return 'bg-yellow-100 text-yellow-700 border-yellow-200';
|
||||||
|
}
|
||||||
|
if (status === '全部未履行') {
|
||||||
|
return 'bg-red-100 text-red-700 border-red-200';
|
||||||
|
}
|
||||||
|
return 'bg-gray-100 text-gray-700 border-gray-200';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DishonestyInfo({ data, paging, loading, error }: DishonestyInfoProps) {
|
||||||
|
// 加载中状态
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg shadow-sm p-6">
|
||||||
|
<div className="flex items-center justify-center py-12">
|
||||||
|
<i className="ri-loader-4-line animate-spin text-2xl text-gray-400 mr-2"></i>
|
||||||
|
<span className="text-gray-500">加载失信信息中...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误状态
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg shadow-sm p-6">
|
||||||
|
<div className="flex items-center justify-center py-12 text-red-500">
|
||||||
|
<i className="ri-error-warning-line text-2xl mr-2"></i>
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无失信记录状态
|
||||||
|
if (!data || data.VerifyResult === 0 || !data.Data || data.Data.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg shadow-sm">
|
||||||
|
<div className="px-6 py-4 border-b border-gray-100">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<i className="ri-shield-check-line text-xl text-[#00684a] mr-2"></i>
|
||||||
|
<h3 className="text-lg font-medium text-gray-900">失信核查</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center py-12">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-16 h-16 mx-auto mb-4 bg-green-100 rounded-full flex items-center justify-center">
|
||||||
|
<i className="ri-shield-check-fill text-3xl text-green-500"></i>
|
||||||
|
</div>
|
||||||
|
<p className="text-green-600 font-medium">暂无失信记录</p>
|
||||||
|
<p className="text-sm text-gray-400 mt-1">该企业/个人未被列入失信被执行人名单</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg shadow-sm">
|
||||||
|
{/* 标题栏 */}
|
||||||
|
<div className="px-6 py-4 border-b border-gray-100">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<i className="ri-error-warning-fill text-xl text-red-500 mr-2"></i>
|
||||||
|
<h3 className="text-lg font-medium text-gray-900">失信核查</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="bg-red-100 text-red-700 px-3 py-1 text-sm rounded-full border border-red-200">
|
||||||
|
存在失信记录
|
||||||
|
</span>
|
||||||
|
{paging && paging.TotalRecords > 0 && (
|
||||||
|
<span className="text-sm text-gray-500">
|
||||||
|
共 <span className="font-medium text-red-600">{paging.TotalRecords}</span> 条记录
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 失信记录列表 */}
|
||||||
|
<div className="px-6 py-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{data.Data.map((record, index) => (
|
||||||
|
<DishonestyRecordCard key={record.Id || index} record={record} index={index + 1} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 分页提示 */}
|
||||||
|
{paging && paging.TotalRecords > paging.PageSize && (
|
||||||
|
<div className="mt-4 text-center text-sm text-gray-500">
|
||||||
|
当前显示第 {paging.PageIndex} 页,共 {Math.ceil(paging.TotalRecords / paging.PageSize)} 页
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 失信记录卡片组件 */
|
||||||
|
interface DishonestyRecordCardProps {
|
||||||
|
record: DishonestyRecord;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DishonestyRecordCard({ record, index }: DishonestyRecordCardProps) {
|
||||||
|
return (
|
||||||
|
<div className="border border-red-200 rounded-lg overflow-hidden">
|
||||||
|
{/* 卡片头部 */}
|
||||||
|
<div className="bg-red-50 px-4 py-3 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className="bg-red-500 text-white text-xs px-2 py-0.5 rounded">
|
||||||
|
#{index}
|
||||||
|
</span>
|
||||||
|
<span className="font-medium text-gray-800">{record.Anno || '案号未知'}</span>
|
||||||
|
</div>
|
||||||
|
<span className={`px-2 py-0.5 text-xs rounded-full border ${getExecuteStatusColor(record.Executestatus)}`}>
|
||||||
|
{record.Executestatus || '状态未知'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 卡片内容 */}
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
<RecordItem
|
||||||
|
icon="ri-bank-line"
|
||||||
|
label="执行法院"
|
||||||
|
value={record.Executegov}
|
||||||
|
/>
|
||||||
|
<RecordItem
|
||||||
|
icon="ri-calendar-line"
|
||||||
|
label="立案日期"
|
||||||
|
value={formatDate(record.Liandate)}
|
||||||
|
/>
|
||||||
|
<RecordItem
|
||||||
|
icon="ri-calendar-check-line"
|
||||||
|
label="发布日期"
|
||||||
|
value={formatDate(record.Publicdate)}
|
||||||
|
/>
|
||||||
|
<RecordItem
|
||||||
|
icon="ri-money-cny-circle-line"
|
||||||
|
label="涉案金额"
|
||||||
|
value={formatAmount(record.Amount)}
|
||||||
|
valueClassName="text-red-600 font-medium"
|
||||||
|
/>
|
||||||
|
<RecordItem
|
||||||
|
icon="ri-file-text-line"
|
||||||
|
label="执行依据文号"
|
||||||
|
value={record.Executeno}
|
||||||
|
className="md:col-span-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 失信行为 */}
|
||||||
|
{record.ActionRemark && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<i className="ri-error-warning-line text-red-500 mt-0.5"></i>
|
||||||
|
<div className="flex-1">
|
||||||
|
<span className="text-xs text-gray-500 block mb-1">失信行为</span>
|
||||||
|
<p className="text-sm text-gray-700 bg-red-50 p-3 rounded-lg border border-red-100">
|
||||||
|
{record.ActionRemark}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 记录项组件 */
|
||||||
|
interface RecordItemProps {
|
||||||
|
icon: string;
|
||||||
|
label: string;
|
||||||
|
value: string | null | undefined;
|
||||||
|
className?: string;
|
||||||
|
valueClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RecordItem({ icon, label, value, className = '', valueClassName = 'text-gray-800' }: RecordItemProps) {
|
||||||
|
return (
|
||||||
|
<div className={`flex items-start gap-2 ${className}`}>
|
||||||
|
<i className={`${icon} text-gray-400 mt-0.5`}></i>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<span className="text-xs text-gray-500 block">{label}</span>
|
||||||
|
<span className={`text-sm ${valueClassName} break-words`}>{value || '-'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 企业信息组件导出文件
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 主组件
|
||||||
|
export { CorporateInformation } from './CorporateInformation';
|
||||||
|
export { BusinessInfo } from './BusinessInfo';
|
||||||
|
export { DishonestyInfo } from './DishonestyInfo';
|
||||||
|
export { CorporateInfoModal } from './CorporateInfoModal';
|
||||||
|
export type { CorporateInfoModalProps } from './CorporateInfoModal';
|
||||||
|
|
||||||
|
// 类型导出
|
||||||
|
export type {
|
||||||
|
// 企业工商信息类型
|
||||||
|
DesignatedRepresentative,
|
||||||
|
OriginalName,
|
||||||
|
RevokeInfo,
|
||||||
|
Area,
|
||||||
|
BusinessInfoResult,
|
||||||
|
BusinessInfoResponse,
|
||||||
|
BusinessInfoProps,
|
||||||
|
// 失信核查类型
|
||||||
|
DishonestyRecord,
|
||||||
|
Paging,
|
||||||
|
DishonestyResult,
|
||||||
|
DishonestyResponse,
|
||||||
|
DishonestyInfoProps,
|
||||||
|
// 综合组件类型
|
||||||
|
CorporateInformationProps,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
// 枚举和常量导出
|
||||||
|
export { EntTypeEnum, EntTypeLabel, ExecuteStatusEnum } from './types';
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
/**
|
||||||
|
* 企业工商信息和失信核查相关类型定义
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ==================== 企业工商信息类型 ====================
|
||||||
|
|
||||||
|
/** 委派代表 */
|
||||||
|
export interface DesignatedRepresentative {
|
||||||
|
/** 合伙人名称 */
|
||||||
|
PartnerName: string;
|
||||||
|
/** 委派代表名称 */
|
||||||
|
DelegatedName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 曾用名 */
|
||||||
|
export interface OriginalName {
|
||||||
|
/** 曾用名 */
|
||||||
|
Name: string;
|
||||||
|
/** 变更日期 */
|
||||||
|
ChangeDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 注销吊销信息 */
|
||||||
|
export interface RevokeInfo {
|
||||||
|
/** 注销日期 */
|
||||||
|
CancelDate: string;
|
||||||
|
/** 注销原因 */
|
||||||
|
CancelReason: string;
|
||||||
|
/** 吊销日期 */
|
||||||
|
RevokeDate: string;
|
||||||
|
/** 吊销原因 */
|
||||||
|
RevokeReason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 行政区域 */
|
||||||
|
export interface Area {
|
||||||
|
/** 省份 */
|
||||||
|
Province: string;
|
||||||
|
/** 城市 */
|
||||||
|
City: string;
|
||||||
|
/** 区域 */
|
||||||
|
County: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 企业性质枚举 */
|
||||||
|
export enum EntTypeEnum {
|
||||||
|
/** 大陆企业 */
|
||||||
|
MainlandEnterprise = '0',
|
||||||
|
/** 社会组织 */
|
||||||
|
SocialOrganization = '1',
|
||||||
|
/** 事业单位 */
|
||||||
|
PublicInstitution = '4',
|
||||||
|
/** 医院 */
|
||||||
|
Hospital = '7',
|
||||||
|
/** 律师事务所 */
|
||||||
|
LawFirm = '9',
|
||||||
|
/** 学校 */
|
||||||
|
School = '10',
|
||||||
|
/** 机关单位 */
|
||||||
|
GovernmentOrgan = '11',
|
||||||
|
/** 其他 */
|
||||||
|
Other = '-1',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 企业性质显示映射 */
|
||||||
|
export const EntTypeLabel: Record<string, string> = {
|
||||||
|
'0': '大陆企业',
|
||||||
|
'1': '社会组织',
|
||||||
|
'4': '事业单位',
|
||||||
|
'7': '医院',
|
||||||
|
'9': '律师事务所',
|
||||||
|
'10': '学校',
|
||||||
|
'11': '机关单位',
|
||||||
|
'-1': '其他',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 企业工商信息结果 */
|
||||||
|
export interface BusinessInfoResult {
|
||||||
|
/** 主键 */
|
||||||
|
KeyNo: string;
|
||||||
|
/** 企业名称 */
|
||||||
|
Name: string;
|
||||||
|
/** 工商注册号/企业编号 */
|
||||||
|
No: string;
|
||||||
|
/** 登记机关 */
|
||||||
|
BelongOrg: string;
|
||||||
|
/** 法定代表人ID */
|
||||||
|
OperId: string;
|
||||||
|
/** 法定代表人名称 */
|
||||||
|
OperName: string;
|
||||||
|
/** 委派代表列表 */
|
||||||
|
DesignatedRepresentativeList: DesignatedRepresentative[];
|
||||||
|
/** 成立日期 */
|
||||||
|
StartDate: string;
|
||||||
|
/** 吊销日期(保留字段) */
|
||||||
|
EndDate: string;
|
||||||
|
/** 登记状态 */
|
||||||
|
Status: string;
|
||||||
|
/** 省份(缩写) */
|
||||||
|
Province: string;
|
||||||
|
/** 更新日期 */
|
||||||
|
UpdatedDate: string;
|
||||||
|
/** 统一社会信用代码/商业登记号码 */
|
||||||
|
CreditCode: string;
|
||||||
|
/** 注册资本(含单位) */
|
||||||
|
RegistCapi: string;
|
||||||
|
/** 注册资本数额 */
|
||||||
|
RegisteredCapital: string;
|
||||||
|
/** 注册资本单位 */
|
||||||
|
RegisteredCapitalUnit: string;
|
||||||
|
/** 注册资本币种 */
|
||||||
|
RegisteredCapitalCCY: string;
|
||||||
|
/** 实缴资本(含单位) */
|
||||||
|
RecCap: string;
|
||||||
|
/** 实缴出资额数额 */
|
||||||
|
PaidUpCapital: string;
|
||||||
|
/** 实缴出资额单位 */
|
||||||
|
PaidUpCapitalUnit: string;
|
||||||
|
/** 实缴出资额币种 */
|
||||||
|
PaidUpCapitalCCY: string;
|
||||||
|
/** 企业类型 */
|
||||||
|
EconKind: string;
|
||||||
|
/** 注册地址 */
|
||||||
|
Address: string;
|
||||||
|
/** 经营范围 */
|
||||||
|
Scope: string;
|
||||||
|
/** 营业期限始 */
|
||||||
|
TermStart: string;
|
||||||
|
/** 营业期限至 */
|
||||||
|
TermEnd: string;
|
||||||
|
/** 核准日期 */
|
||||||
|
CheckDate: string;
|
||||||
|
/** 组织机构代码 */
|
||||||
|
OrgNo: string;
|
||||||
|
/** 是否上市(0-未上市,1-上市) */
|
||||||
|
IsOnStock: string;
|
||||||
|
/** 股票代码 */
|
||||||
|
StockNumber: string | null;
|
||||||
|
/** 上市类型 */
|
||||||
|
StockType: string | null;
|
||||||
|
/** 曾用名列表 */
|
||||||
|
OriginalName: OriginalName[];
|
||||||
|
/** 企业Logo地址 */
|
||||||
|
ImageUrl: string;
|
||||||
|
/** 企业性质 */
|
||||||
|
EntType: string;
|
||||||
|
/** 注销吊销信息 */
|
||||||
|
RevokeInfo: RevokeInfo | null;
|
||||||
|
/** 行政区域 */
|
||||||
|
Area: Area | null;
|
||||||
|
/** 行政区划代码 */
|
||||||
|
AreaCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 企业工商信息响应 */
|
||||||
|
export interface BusinessInfoResponse {
|
||||||
|
/** 查询结果数据 */
|
||||||
|
Result: BusinessInfoResult | null;
|
||||||
|
/** 状态码(200 表示成功) */
|
||||||
|
Status: string;
|
||||||
|
/** 响应消息 */
|
||||||
|
Message: string;
|
||||||
|
/** 订单编号 */
|
||||||
|
OrderNumber: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 失信核查类型 ====================
|
||||||
|
|
||||||
|
/** 失信记录数据 */
|
||||||
|
export interface DishonestyRecord {
|
||||||
|
/** 主键 */
|
||||||
|
Id: string;
|
||||||
|
/** 立案日期 */
|
||||||
|
Liandate: string;
|
||||||
|
/** 案号 */
|
||||||
|
Anno: string;
|
||||||
|
/** 执行法院 */
|
||||||
|
Executegov: string;
|
||||||
|
/** 被执行人的履行情况 */
|
||||||
|
Executestatus: string;
|
||||||
|
/** 发布日期 */
|
||||||
|
Publicdate: string;
|
||||||
|
/** 执行依据文号 */
|
||||||
|
Executeno: string;
|
||||||
|
/** 失信行为 */
|
||||||
|
ActionRemark: string;
|
||||||
|
/** 涉案金额(元) */
|
||||||
|
Amount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 分页信息 */
|
||||||
|
export interface Paging {
|
||||||
|
/** 每页记录数 */
|
||||||
|
PageSize: number;
|
||||||
|
/** 当前页码 */
|
||||||
|
PageIndex: number;
|
||||||
|
/** 总记录数 */
|
||||||
|
TotalRecords: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 失信核查结果 */
|
||||||
|
export interface DishonestyResult {
|
||||||
|
/** 数据是否存在(1-存在,0-不存在) */
|
||||||
|
VerifyResult: number;
|
||||||
|
/** 数据信息列表 */
|
||||||
|
Data: DishonestyRecord[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 失信核查响应 */
|
||||||
|
export interface DishonestyResponse {
|
||||||
|
/** 分页信息 */
|
||||||
|
Paging: Paging;
|
||||||
|
/** 查询结果数据 */
|
||||||
|
Result: DishonestyResult | null;
|
||||||
|
/** 状态码(200 表示成功) */
|
||||||
|
Status: string;
|
||||||
|
/** 响应消息 */
|
||||||
|
Message: string;
|
||||||
|
/** 订单编号 */
|
||||||
|
OrderNumber: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 履行情况枚举 */
|
||||||
|
export enum ExecuteStatusEnum {
|
||||||
|
/** 全部未履行 */
|
||||||
|
NotExecuted = '全部未履行',
|
||||||
|
/** 部分履行 */
|
||||||
|
PartiallyExecuted = '部分履行',
|
||||||
|
/** 全部履行 */
|
||||||
|
FullyExecuted = '全部履行',
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 组件Props类型 ====================
|
||||||
|
|
||||||
|
/** 企业工商信息组件Props */
|
||||||
|
export interface BusinessInfoProps {
|
||||||
|
/** 企业工商信息数据 */
|
||||||
|
data: BusinessInfoResult | null;
|
||||||
|
/** 是否加载中 */
|
||||||
|
loading?: boolean;
|
||||||
|
/** 错误信息 */
|
||||||
|
error?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 失信核查组件Props */
|
||||||
|
export interface DishonestyInfoProps {
|
||||||
|
/** 失信核查数据 */
|
||||||
|
data: DishonestyResult | null;
|
||||||
|
/** 分页信息 */
|
||||||
|
paging?: Paging | null;
|
||||||
|
/** 是否加载中 */
|
||||||
|
loading?: boolean;
|
||||||
|
/** 错误信息 */
|
||||||
|
error?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 企业综合信息组件Props */
|
||||||
|
export interface CorporateInformationProps {
|
||||||
|
/** 企业工商信息 */
|
||||||
|
businessInfo: BusinessInfoResult | null;
|
||||||
|
/** 失信核查数据 */
|
||||||
|
dishonestyInfo: DishonestyResult | null;
|
||||||
|
/** 失信核查分页信息 */
|
||||||
|
dishonestyPaging?: Paging | null;
|
||||||
|
/** 企业工商信息加载中 */
|
||||||
|
businessLoading?: boolean;
|
||||||
|
/** 失信核查加载中 */
|
||||||
|
dishonestyLoading?: boolean;
|
||||||
|
/** 企业工商信息错误 */
|
||||||
|
businessError?: string | null;
|
||||||
|
/** 失信核查错误 */
|
||||||
|
dishonestyError?: string | null;
|
||||||
|
}
|
||||||
@@ -21,6 +21,9 @@ import { useState, useEffect, useRef } from 'react';
|
|||||||
import { toastService } from '../ui/Toast';
|
import { toastService } from '../ui/Toast';
|
||||||
import { createPortal } from 'react-dom'; // 导入React Portal API,用于将组件渲染到DOM树的不同位置
|
import { createPortal } from 'react-dom'; // 导入React Portal API,用于将组件渲染到DOM树的不同位置
|
||||||
import { Tooltip } from '../ui/Tooltip';
|
import { Tooltip } from '../ui/Tooltip';
|
||||||
|
import { CorporateInfoModal } from '../corporate-information';
|
||||||
|
import type { BusinessInfoResult, DishonestyResult } from '../corporate-information';
|
||||||
|
import { queryCompanyInfo } from '~/api/corporate-information/qichacha';
|
||||||
// import '../../styles/components/TooltipStyles.css';
|
// import '../../styles/components/TooltipStyles.css';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -423,6 +426,15 @@ export function ReviewPointsList({
|
|||||||
// 存放评查点ID与有效页码的映射
|
// 存放评查点ID与有效页码的映射
|
||||||
const [effectivePages, setEffectivePages] = useState<Record<string, number>>({});
|
const [effectivePages, setEffectivePages] = useState<Record<string, number>>({});
|
||||||
|
|
||||||
|
// 企业信息模态框状态
|
||||||
|
const [corporateModalVisible, setCorporateModalVisible] = useState(false);
|
||||||
|
const [corporateCompanyName, setCorporateCompanyName] = useState('');
|
||||||
|
const [corporateBusinessInfo, setCorporateBusinessInfo] = useState<BusinessInfoResult | null>(null);
|
||||||
|
const [corporateDishonestyInfo, setCorporateDishonestyInfo] = useState<DishonestyResult | null>(null);
|
||||||
|
const [corporateLoading, setCorporateLoading] = useState(false);
|
||||||
|
const [corporateError, setCorporateError] = useState<string | null>(null);
|
||||||
|
const [corporateUpdatedAt, setCorporateUpdatedAt] = useState<string | null>(null);
|
||||||
|
|
||||||
// 初始化建议文本
|
// 初始化建议文本
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 使用函数式更新,不再需要外部 manualReviewNotes 变量
|
// 使用函数式更新,不再需要外部 manualReviewNotes 变量
|
||||||
@@ -483,6 +495,71 @@ export function ReviewPointsList({
|
|||||||
setEditingReviewPoint(null);
|
setEditingReviewPoint(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理企业信息按钮点击
|
||||||
|
* @param companyName 企业名称(乙方名称)
|
||||||
|
* @param forceRefresh 是否强制刷新(对接企查查重新查询)
|
||||||
|
*/
|
||||||
|
const handleCorporateInfoClick = async (companyName: string, forceRefresh = false) => {
|
||||||
|
if (!companyName) {
|
||||||
|
toastService.warning('企业名称为空,无法查询');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开模态框并设置加载状态
|
||||||
|
setCorporateModalVisible(true);
|
||||||
|
setCorporateCompanyName(companyName);
|
||||||
|
setCorporateLoading(true);
|
||||||
|
setCorporateError(null);
|
||||||
|
setCorporateBusinessInfo(null);
|
||||||
|
setCorporateDishonestyInfo(null);
|
||||||
|
setCorporateUpdatedAt(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await queryCompanyInfo({ keyword: companyName, force_refresh: forceRefresh });
|
||||||
|
|
||||||
|
if (response.success && response.data) {
|
||||||
|
setCorporateBusinessInfo(response.data.enterprise);
|
||||||
|
setCorporateUpdatedAt(response.data.updated_at);
|
||||||
|
// 转换失信数据格式
|
||||||
|
if (response.data.dishonesty) {
|
||||||
|
setCorporateDishonestyInfo({
|
||||||
|
VerifyResult: response.data.dishonesty.VerifyResult,
|
||||||
|
Data: response.data.dishonesty.Data || [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCorporateError(response.message || '查询失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('查询企业信息失败:', error);
|
||||||
|
setCorporateError(error instanceof Error ? error.message : '查询失败');
|
||||||
|
} finally {
|
||||||
|
setCorporateLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理强制刷新(对接企查查重新查询)
|
||||||
|
*/
|
||||||
|
const handleCorporateForceRefresh = async () => {
|
||||||
|
if (corporateCompanyName) {
|
||||||
|
await handleCorporateInfoClick(corporateCompanyName, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭企业信息模态框
|
||||||
|
*/
|
||||||
|
const handleCloseCorporateModal = () => {
|
||||||
|
setCorporateModalVisible(false);
|
||||||
|
setCorporateCompanyName('');
|
||||||
|
setCorporateBusinessInfo(null);
|
||||||
|
setCorporateDishonestyInfo(null);
|
||||||
|
setCorporateError(null);
|
||||||
|
setCorporateUpdatedAt(null);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 过滤评查点
|
* 过滤评查点
|
||||||
* 根据搜索文本和状态过滤条件筛选评查点
|
* 根据搜索文本和状态过滤条件筛选评查点
|
||||||
@@ -2406,7 +2483,7 @@ export function ReviewPointsList({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
style={{ userSelect: 'text' }}
|
style={{ userSelect: 'text' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// console.log('reviewPoint', reviewPoint);
|
console.log('reviewPoint', reviewPoint);
|
||||||
handleReviewPointClick(reviewPoint.id);
|
handleReviewPointClick(reviewPoint.id);
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
@@ -2419,7 +2496,51 @@ export function ReviewPointsList({
|
|||||||
{/* 评查点名称 pointName*/}
|
{/* 评查点名称 pointName*/}
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="flex justify-between items-center mb-2">
|
||||||
{/* <div className='flex flex-col'> */}
|
{/* <div className='flex flex-col'> */}
|
||||||
<div className="review-point-title text-left text-blue-500 max-w-[75%] break-all">{reviewPoint.pointName}</div>
|
<div className="flex items-center gap-2 max-w-[75%]">
|
||||||
|
<div className="review-point-title text-left text-blue-500 break-all">{reviewPoint.pointName}</div>
|
||||||
|
{ reviewPoint.pointName === '签署乙方详细信息校验' && (
|
||||||
|
<button
|
||||||
|
className="enterprise-info-btn"
|
||||||
|
style={{
|
||||||
|
padding: '2px 8px',
|
||||||
|
fontSize: '12px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
flexShrink: 0,
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
border: 'none',
|
||||||
|
cursor: reviewPoint.content?.['合同主体信息-乙方-名称']?.value ? 'pointer' : 'not-allowed',
|
||||||
|
backgroundColor: reviewPoint.content?.['合同主体信息-乙方-名称']?.value ? '#00684a' : '#e5e7eb',
|
||||||
|
color: reviewPoint.content?.['合同主体信息-乙方-名称']?.value ? '#ffffff' : '#9ca3af',
|
||||||
|
}}
|
||||||
|
disabled={!reviewPoint.content?.['合同主体信息-乙方-名称']?.value}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const companyNameValue = reviewPoint.content?.['合同主体信息-乙方-名称']?.value;
|
||||||
|
const companyName = typeof companyNameValue === 'string' ? companyNameValue : String(companyNameValue || '');
|
||||||
|
if (companyName) {
|
||||||
|
handleCorporateInfoClick(companyName);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
if (reviewPoint.content?.['合同主体信息-乙方-名称']?.value) {
|
||||||
|
e.currentTarget.style.backgroundColor = '#005a3f';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
if (reviewPoint.content?.['合同主体信息-乙方-名称']?.value) {
|
||||||
|
e.currentTarget.style.backgroundColor = '#00684a';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="ri-eye-line"></i>
|
||||||
|
企业信息
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
{/* <div className="review-point-header flex justify-between items-start">
|
{/* <div className="review-point-header flex justify-between items-start">
|
||||||
<div className="flex-1 text-left min-w-[25%] font-medium text-[13px]">{reviewPoint.title}</div>
|
<div className="flex-1 text-left min-w-[25%] font-medium text-[13px]">{reviewPoint.title}</div>
|
||||||
//评查点分组显示
|
//评查点分组显示
|
||||||
@@ -2450,6 +2571,21 @@ export function ReviewPointsList({
|
|||||||
renderEmptyState()
|
renderEmptyState()
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 企业信息模态框 */}
|
||||||
|
<CorporateInfoModal
|
||||||
|
visible={corporateModalVisible}
|
||||||
|
onClose={handleCloseCorporateModal}
|
||||||
|
companyName={corporateCompanyName}
|
||||||
|
businessInfo={corporateBusinessInfo}
|
||||||
|
dishonestyInfo={corporateDishonestyInfo}
|
||||||
|
businessLoading={corporateLoading}
|
||||||
|
dishonestyLoading={corporateLoading}
|
||||||
|
businessError={corporateError}
|
||||||
|
dishonestyError={corporateError}
|
||||||
|
updatedAt={corporateUpdatedAt}
|
||||||
|
onForceRefresh={handleCorporateForceRefresh}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -451,3 +451,19 @@ if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'testing'
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交叉评查专属模式配置
|
||||||
|
* 当为 true 且端口为 51707 时,首页只显示交叉评查入口
|
||||||
|
*/
|
||||||
|
export const CROSS_CHECKING_ONLY_MODE = process.env.CROSS_CHECKING_ONLY_MODE === 'true';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交叉评查专属模式的目标端口
|
||||||
|
*/
|
||||||
|
export const CROSS_CHECKING_ONLY_PORT = '51707';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前端口(服务端使用)
|
||||||
|
*/
|
||||||
|
export { getCurrentPort };
|
||||||
@@ -443,7 +443,11 @@ export function ErrorBoundary() {
|
|||||||
statusText={statusText}
|
statusText={statusText}
|
||||||
message={message}
|
message={message}
|
||||||
/>
|
/>
|
||||||
|
{/* 添加 RouteChangeLoader 确保导航状态变化时能正确隐藏加载条 */}
|
||||||
|
<RouteChangeLoader />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
|
{/* 添加 LoadingBarContainer 确保加载条能正确显示和隐藏 */}
|
||||||
|
<LoadingBarContainer />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
+64
-6
@@ -5,7 +5,7 @@ import styles from "~/styles/pages/home.css?url";
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getUserSession, logout } from "~/api/login/auth.server";
|
import { getUserSession, logout } from "~/api/login/auth.server";
|
||||||
import { toastService } from '~/components/ui';
|
import { toastService } from '~/components/ui';
|
||||||
import { DOCUMENT_URL } from '~/config/api-config';
|
import { DOCUMENT_URL, CROSS_CHECKING_ONLY_MODE, CROSS_CHECKING_ONLY_PORT, getCurrentPort } from '~/config/api-config';
|
||||||
|
|
||||||
export const links = () => [
|
export const links = () => [
|
||||||
{ rel: "stylesheet", href: styles }
|
{ rel: "stylesheet", href: styles }
|
||||||
@@ -87,8 +87,26 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔑 判断是否启用交叉评查专属模式
|
||||||
|
// 条件:CROSS_CHECKING_ONLY_MODE=true 且 当前端口为 51707
|
||||||
|
const currentPort = getCurrentPort();
|
||||||
|
const isCrossCheckingOnlyMode = CROSS_CHECKING_ONLY_MODE && currentPort === CROSS_CHECKING_ONLY_PORT;
|
||||||
|
|
||||||
|
if (isCrossCheckingOnlyMode) {
|
||||||
|
console.log(`🔒 [Index Loader] 交叉评查专属模式已启用 (端口: ${currentPort})`);
|
||||||
|
}
|
||||||
|
|
||||||
// 返回用户信息、入口模块和权限给客户端
|
// 返回用户信息、入口模块和权限给客户端
|
||||||
return Response.json({ userRole, userInfo, entryModules, hasSettingsAccess, hasCrossCheckingAccess, hasChatLLMAccess, settingsChildren });
|
return Response.json({
|
||||||
|
userRole,
|
||||||
|
userInfo,
|
||||||
|
entryModules,
|
||||||
|
hasSettingsAccess,
|
||||||
|
hasCrossCheckingAccess,
|
||||||
|
hasChatLLMAccess,
|
||||||
|
settingsChildren,
|
||||||
|
isCrossCheckingOnlyMode // 新增:交叉评查专属模式标志
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
@@ -326,8 +344,8 @@ export default function Index() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="user-info">
|
<div className="user-info">
|
||||||
{/* 系统设置按钮 - 只在有权限时显示 */}
|
{/* 系统设置按钮 - 只在有权限且非交叉评查专属模式时显示 */}
|
||||||
{loaderData.hasSettingsAccess && (
|
{loaderData.hasSettingsAccess && !loaderData.isCrossCheckingOnlyMode && (
|
||||||
<button
|
<button
|
||||||
onClick={handleEnterSettings}
|
onClick={handleEnterSettings}
|
||||||
className="settings-button"
|
className="settings-button"
|
||||||
@@ -369,8 +387,47 @@ export default function Index() {
|
|||||||
|
|
||||||
{/* 模块网格区域 */}
|
{/* 模块网格区域 */}
|
||||||
<div className="modules-container">
|
<div className="modules-container">
|
||||||
{/* 动态渲染入口模块 */}
|
{/* 🔒 交叉评查专属模式:只显示交叉评查入口 */}
|
||||||
{loaderData.entryModules && loaderData.entryModules.length > 0 ? (
|
{loaderData.isCrossCheckingOnlyMode ? (
|
||||||
|
loaderData.hasCrossCheckingAccess ? (
|
||||||
|
<div
|
||||||
|
className="module-card"
|
||||||
|
onClick={handleEnterCrossChecking}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
handleEnterCrossChecking();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="交叉评查"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/images/icon_cross@2x.png"
|
||||||
|
alt="交叉评查"
|
||||||
|
className="w-12 h-12 mx-1"
|
||||||
|
onError={(e) => {
|
||||||
|
(e.target as HTMLImageElement).style.display = 'none';
|
||||||
|
const parent = (e.target as HTMLImageElement).parentElement;
|
||||||
|
if (parent) {
|
||||||
|
const icon = document.createElement('i');
|
||||||
|
icon.className = 'ri-shuffle-line';
|
||||||
|
icon.style.fontSize = '48px';
|
||||||
|
icon.style.color = 'var(--color-primary)';
|
||||||
|
parent.insertBefore(icon, parent.firstChild);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="module-name">交叉评查</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center text-gray-500 py-8">
|
||||||
|
暂无可用模块
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
/* 正常模式:显示所有入口模块 */
|
||||||
|
loaderData.entryModules && loaderData.entryModules.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{loaderData.entryModules.map((module) => {
|
{loaderData.entryModules.map((module) => {
|
||||||
// 判断是否为智慧法务助手,如果是且有交叉评查权限,则在其之前插入交叉评查卡片
|
// 判断是否为智慧法务助手,如果是且有交叉评查权限,则在其之前插入交叉评查卡片
|
||||||
@@ -442,6 +499,7 @@ export default function Index() {
|
|||||||
<div className="text-center text-gray-500 py-8">
|
<div className="text-center text-gray-500 py-8">
|
||||||
暂无可用模块
|
暂无可用模块
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { MetaFunction, LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node';
|
import type { MetaFunction, LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node';
|
||||||
import { redirect } from '@remix-run/node';
|
import { redirect } from '@remix-run/node';
|
||||||
import { useLoaderData, useNavigate, useSubmit } from '@remix-run/react';
|
import { useLoaderData, useNavigate, useSubmit, useActionData } from '@remix-run/react';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { getContractTemplate } from '~/api/contract-template/templates';
|
import { getContractTemplate } from '~/api/contract-template/templates';
|
||||||
import type { ContractTemplate } from '~/api/contract-template/templates';
|
import type { ContractTemplate } from '~/api/contract-template/templates';
|
||||||
@@ -9,6 +9,7 @@ import filePreviewStyles from '~/styles/components/file-preview-isolation.css?ur
|
|||||||
import { getUserSession } from '~/api/login/auth.server';
|
import { getUserSession } from '~/api/login/auth.server';
|
||||||
import { createDraftContract } from '~/api/contracts/draft-service.server';
|
import { createDraftContract } from '~/api/contracts/draft-service.server';
|
||||||
import { apiRequest, downloadFile } from '~/api/axios-client';
|
import { apiRequest, downloadFile } from '~/api/axios-client';
|
||||||
|
import { checkRoutePermission } from '~/api/auth/check-route-permission.server';
|
||||||
|
|
||||||
// 导入FilePreview组件
|
// 导入FilePreview组件
|
||||||
import { FilePreview } from '~/components/reviews';
|
import { FilePreview } from '~/components/reviews';
|
||||||
@@ -76,11 +77,20 @@ export async function action({ request, params }: ActionFunctionArgs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息和JWT
|
// 获取用户信息和JWT
|
||||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
const { userInfo, frontendJWT, userRole } = await getUserSession(request);
|
||||||
if (!userInfo?.sub) {
|
if (!userInfo?.sub) {
|
||||||
return Response.json({ error: '未登录' }, { status: 401 });
|
return Response.json({ error: '未登录' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔒 在执行任何操作之前,先检查用户是否有权限访问目标路由
|
||||||
|
const targetPath = '/contract-draft';
|
||||||
|
const permissionCheck = await checkRoutePermission(targetPath, userRole, frontendJWT || undefined);
|
||||||
|
|
||||||
|
if (!permissionCheck.allowed) {
|
||||||
|
console.warn(`[Action] 用户无权访问 ${targetPath}:`, permissionCheck.error);
|
||||||
|
return Response.json({ error: permissionCheck.error || '您没有权限使用起草合同功能' }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 解析表单数据
|
// 解析表单数据
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
@@ -148,8 +158,14 @@ export async function action({ request, params }: ActionFunctionArgs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Action 返回的数据类型
|
||||||
|
interface ActionData {
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ContractTemplateDetail() {
|
export default function ContractTemplateDetail() {
|
||||||
const { template }: { template: ContractTemplate } = useLoaderData<typeof loader>();
|
const { template }: { template: ContractTemplate } = useLoaderData<typeof loader>();
|
||||||
|
const actionData = useActionData<ActionData>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const submit = useSubmit();
|
const submit = useSubmit();
|
||||||
const [isCreatingDraft, setIsCreatingDraft] = useState(false);
|
const [isCreatingDraft, setIsCreatingDraft] = useState(false);
|
||||||
@@ -162,6 +178,14 @@ export default function ContractTemplateDetail() {
|
|||||||
window.scrollTo({ top: 0, behavior: 'instant' });
|
window.scrollTo({ top: 0, behavior: 'instant' });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 处理 action 返回的错误
|
||||||
|
useEffect(() => {
|
||||||
|
if (actionData?.error) {
|
||||||
|
toastService.error(actionData.error);
|
||||||
|
setIsCreatingDraft(false);
|
||||||
|
}
|
||||||
|
}, [actionData]);
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
navigate('/contract-template/list');
|
navigate('/contract-template/list');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||||||
}, { status: 500 });
|
}, { status: 500 });
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('用户任务详情返回:', response.data);
|
// console.log('用户任务详情返回:', response.data);
|
||||||
return Response.json({
|
return Response.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: response.data
|
data: response.data
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ export default function DocumentTypesList() {
|
|||||||
const canCreateType = canCreate('document_type');
|
const canCreateType = canCreate('document_type');
|
||||||
const canUpdateType = canUpdate('document_type');
|
const canUpdateType = canUpdate('document_type');
|
||||||
const canDeleteType = canDelete('document_type');
|
const canDeleteType = canDelete('document_type');
|
||||||
|
// console.log('document_type---canDeleteType',canDeleteType)
|
||||||
const canViewType = canView('document_type');
|
const canViewType = canView('document_type');
|
||||||
|
|
||||||
// 获取搜索参数
|
// 获取搜索参数
|
||||||
@@ -299,7 +300,7 @@ export default function DocumentTypesList() {
|
|||||||
{
|
{
|
||||||
title: "文档类型名称",
|
title: "文档类型名称",
|
||||||
key: "name",
|
key: "name",
|
||||||
width: "200px",
|
width: "220px",
|
||||||
render: (_: unknown, record: DocumentTypeUI) => (
|
render: (_: unknown, record: DocumentTypeUI) => (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<i className="ri-file-text-line text-primary mr-2"></i>
|
<i className="ri-file-text-line text-primary mr-2"></i>
|
||||||
@@ -310,10 +311,10 @@ export default function DocumentTypesList() {
|
|||||||
{
|
{
|
||||||
title: "描述",
|
title: "描述",
|
||||||
key: "description",
|
key: "description",
|
||||||
width: "250px",
|
width: "260px",
|
||||||
render: (_: unknown, record: DocumentTypeUI) => (
|
render: (_: unknown, record: DocumentTypeUI) => (
|
||||||
<div className="text-secondary text-sm truncate max-w-xs" title={record.description}>
|
<div className="text-secondary text-sm truncate max-w-[300px]" title={record.description}>
|
||||||
{record.description}
|
{record.description || '-'}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -326,7 +327,7 @@ export default function DocumentTypesList() {
|
|||||||
{record.entry_module ? (
|
{record.entry_module ? (
|
||||||
<span className="entry-module-badge">{record.entry_module.name}</span>
|
<span className="entry-module-badge">{record.entry_module.name}</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-gray-400">暂无关联入口</span>
|
<span className="text-gray-400">-</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -343,7 +344,7 @@ export default function DocumentTypesList() {
|
|||||||
</span>
|
</span>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<span className="text-gray-400">暂无关联分组</span>
|
<span className="text-gray-400">-</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -351,19 +352,19 @@ export default function DocumentTypesList() {
|
|||||||
{
|
{
|
||||||
title: "创建时间",
|
title: "创建时间",
|
||||||
key: "created_at",
|
key: "created_at",
|
||||||
width: "150px",
|
width: "160px",
|
||||||
render: (_: unknown, record: DocumentTypeUI) => record.created_at
|
render: (_: unknown, record: DocumentTypeUI) => record.created_at || '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "更新时间",
|
title: "更新时间",
|
||||||
key: "updated_at",
|
key: "updated_at",
|
||||||
width: "150px",
|
width: "160px",
|
||||||
render: (_: unknown, record: DocumentTypeUI) => record.updated_at
|
render: (_: unknown, record: DocumentTypeUI) => record.updated_at || '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
key: "operation",
|
key: "operation",
|
||||||
width: "150px",
|
width: "160px",
|
||||||
render: (_: unknown, record: DocumentTypeUI) => (
|
render: (_: unknown, record: DocumentTypeUI) => (
|
||||||
<div className="operations-cell">
|
<div className="operations-cell">
|
||||||
{canViewType && (
|
{canViewType && (
|
||||||
@@ -379,7 +380,7 @@ export default function DocumentTypesList() {
|
|||||||
)}
|
)}
|
||||||
{canDeleteType && (
|
{canDeleteType && (
|
||||||
<button
|
<button
|
||||||
className="operation-btn text-error !hidden"
|
className="operation-btn text-error"
|
||||||
onClick={() => handleDelete(record.id)}
|
onClick={() => handleDelete(record.id)}
|
||||||
disabled={isDeleting}
|
disabled={isDeleting}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -477,6 +477,8 @@ export default function DocumentTypeNew() {
|
|||||||
} else if (name === 'vlm_extraction_template') {
|
} else if (name === 'vlm_extraction_template') {
|
||||||
fieldName = 'vlmExtractionTemplateId';
|
fieldName = 'vlmExtractionTemplateId';
|
||||||
setTouchedFields(prev => ({...prev, vlmExtractionTemplate: true}));
|
setTouchedFields(prev => ({...prev, vlmExtractionTemplate: true}));
|
||||||
|
} else if (name === 'entry_module_id') {
|
||||||
|
fieldName = 'entryModuleId';
|
||||||
} else if (name === 'name') {
|
} else if (name === 'name') {
|
||||||
setTouchedFields(prev => ({...prev, name: true}));
|
setTouchedFields(prev => ({...prev, name: true}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useLoaderData, useActionData, useNavigate, Form } from "@remix-run/reac
|
|||||||
import { redirect, type ActionFunctionArgs, type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node";
|
import { redirect, type ActionFunctionArgs, type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node";
|
||||||
import { Card } from "~/components/ui/Card";
|
import { Card } from "~/components/ui/Card";
|
||||||
import { Button } from "~/components/ui/Button";
|
import { Button } from "~/components/ui/Button";
|
||||||
|
import { usePermission } from "~/hooks/usePermission";
|
||||||
import documentEditStyles from "~/styles/pages/documents_edit.css?url";
|
import documentEditStyles from "~/styles/pages/documents_edit.css?url";
|
||||||
import { getDocument, updateDocument } from "~/api/files/documents";
|
import { getDocument, updateDocument } from "~/api/files/documents";
|
||||||
import { getDocumentTypes } from "~/api/document-types/document-types";
|
import { getDocumentTypes } from "~/api/document-types/document-types";
|
||||||
@@ -209,6 +210,10 @@ export default function DocumentEdit() {
|
|||||||
const [loadError, setLoadError] = useState<string | null>(null);
|
const [loadError, setLoadError] = useState<string | null>(null);
|
||||||
const formRef = useRef<HTMLFormElement>(null);
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
|
// 权限控制
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
const canUpdate = hasPermission('document:document:update');
|
||||||
|
|
||||||
// 表单状态管理 - 使用受控组件
|
// 表单状态管理 - 使用受控组件
|
||||||
const [formValues, setFormValues] = useState({
|
const [formValues, setFormValues] = useState({
|
||||||
documentNumber: documentData.documentNumber || "",
|
documentNumber: documentData.documentNumber || "",
|
||||||
@@ -476,6 +481,8 @@ export default function DocumentEdit() {
|
|||||||
>
|
>
|
||||||
返回列表
|
返回列表
|
||||||
</Button>
|
</Button>
|
||||||
|
{/* 保存修改按钮 - 需要 document:document:update 权限 */}
|
||||||
|
{canUpdate && (
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon="ri-save-line"
|
icon="ri-save-line"
|
||||||
@@ -483,6 +490,7 @@ export default function DocumentEdit() {
|
|||||||
>
|
>
|
||||||
保存修改
|
保存修改
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useSearchParams, useLoaderData, useFetcher, useNavigate,Link } from "@r
|
|||||||
import { type MetaFunction, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node";
|
import { type MetaFunction, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node";
|
||||||
import { Card } from "~/components/ui/Card";
|
import { Card } from "~/components/ui/Card";
|
||||||
import { Button } from "~/components/ui/Button";
|
import { Button } from "~/components/ui/Button";
|
||||||
|
import { usePermission, PermissionGuard } from "~/hooks/usePermission";
|
||||||
// import { Table } from "~/components/ui/Table";
|
// import { Table } from "~/components/ui/Table";
|
||||||
import { Pagination } from "~/components/ui/Pagination";
|
import { Pagination } from "~/components/ui/Pagination";
|
||||||
import { FileTypeTag } from "~/components/ui/FileTypeTag";
|
import { FileTypeTag } from "~/components/ui/FileTypeTag";
|
||||||
@@ -184,6 +185,11 @@ export default function DocumentsIndex() {
|
|||||||
const fetcher = useFetcher<ActionResponse>();
|
const fetcher = useFetcher<ActionResponse>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// 权限控制
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
const canView = hasPermission('document:document:view');
|
||||||
|
const canUpdate = hasPermission('document:document:update');
|
||||||
|
|
||||||
// 存储从 sessionStorage 获取的 documentTypeIds
|
// 存储从 sessionStorage 获取的 documentTypeIds
|
||||||
const [documentTypeIds, setDocumentTypeIds] = useState<number[] | null>(null);
|
const [documentTypeIds, setDocumentTypeIds] = useState<number[] | null>(null);
|
||||||
|
|
||||||
@@ -1172,6 +1178,8 @@ export default function DocumentsIndex() {
|
|||||||
<td className="text-xs text-gray-600 px-4 py-3" style={{ width: '10%' }}>{historyDoc.uploadTime}</td>
|
<td className="text-xs text-gray-600 px-4 py-3" style={{ width: '10%' }}>{historyDoc.uploadTime}</td>
|
||||||
<td className="px-4 py-3" style={{ width: '25%' }}>
|
<td className="px-4 py-3" style={{ width: '25%' }}>
|
||||||
<div className="operations-cell flex flex-wrap gap-1">
|
<div className="operations-cell flex flex-wrap gap-1">
|
||||||
|
{/* 查看按钮 - 需要 document:document:view 权限 */}
|
||||||
|
{canView && (
|
||||||
<Link
|
<Link
|
||||||
to={`/reviews?id=${historyDoc.id}&previousRoute=documents`}
|
to={`/reviews?id=${historyDoc.id}&previousRoute=documents`}
|
||||||
className="text-xs px-2 py-1 h-7 mr-1 hover:underline"
|
className="text-xs px-2 py-1 h-7 mr-1 hover:underline"
|
||||||
@@ -1179,6 +1187,9 @@ export default function DocumentsIndex() {
|
|||||||
<i className="ri-eye-line"></i>
|
<i className="ri-eye-line"></i>
|
||||||
查看
|
查看
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
|
{/* 修改按钮 - 需要 document:document:view 权限 */}
|
||||||
|
{canView && (
|
||||||
<Link
|
<Link
|
||||||
to={`/documents/edit?id=${historyDoc.id}`}
|
to={`/documents/edit?id=${historyDoc.id}`}
|
||||||
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
||||||
@@ -1186,6 +1197,9 @@ export default function DocumentsIndex() {
|
|||||||
<i className="ri-edit-line"></i>
|
<i className="ri-edit-line"></i>
|
||||||
修改
|
修改
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
|
{/* 下载按钮 - 需要 document:document:view 权限 */}
|
||||||
|
{canView && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
||||||
@@ -1194,7 +1208,9 @@ export default function DocumentsIndex() {
|
|||||||
<i className="ri-download-line"></i>
|
<i className="ri-download-line"></i>
|
||||||
下载
|
下载
|
||||||
</button>
|
</button>
|
||||||
{parentDoc.type === '1' && historyDoc.fileStatus === 'Processed' && (
|
)}
|
||||||
|
{/* 追加附件和上传模板按钮 - 需要 document:document:view 权限 */}
|
||||||
|
{canView && parentDoc.type === '1' && historyDoc.fileStatus === 'Processed' && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1224,6 +1240,8 @@ export default function DocumentsIndex() {
|
|||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{/* 删除按钮 - 需要 document:document:view 权限 */}
|
||||||
|
{canView && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="text-xs px-2 py-1 h-7 text-error hover:underline hover:text-red-700"
|
className="text-xs px-2 py-1 h-7 text-error hover:underline hover:text-red-700"
|
||||||
@@ -1232,6 +1250,7 @@ export default function DocumentsIndex() {
|
|||||||
<i className="ri-delete-bin-line"></i>
|
<i className="ri-delete-bin-line"></i>
|
||||||
删除
|
删除
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1393,6 +1412,9 @@ export default function DocumentsIndex() {
|
|||||||
width: "25%",
|
width: "25%",
|
||||||
render: (_: unknown, record: DocumentUI) => (
|
render: (_: unknown, record: DocumentUI) => (
|
||||||
<div className="operations-cell flex flex-wrap gap-1">
|
<div className="operations-cell flex flex-wrap gap-1">
|
||||||
|
{/* 查看/开始审核按钮 - 需要 document:document:view 权限 */}
|
||||||
|
{canView && (
|
||||||
|
<>
|
||||||
{(record.auditStatus === 0 || record.auditStatus == null) ? (
|
{(record.auditStatus === 0 || record.auditStatus == null) ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleReviewFileClick(record.id, record.auditStatus)}
|
onClick={() => handleReviewFileClick(record.id, record.auditStatus)}
|
||||||
@@ -1424,6 +1446,10 @@ export default function DocumentsIndex() {
|
|||||||
查看
|
查看
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{/* 修改按钮 - 需要 document:document:view 权限 */}
|
||||||
|
{canView && (
|
||||||
<Link
|
<Link
|
||||||
to={`/documents/edit?id=${record.id}`}
|
to={`/documents/edit?id=${record.id}`}
|
||||||
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
||||||
@@ -1431,6 +1457,9 @@ export default function DocumentsIndex() {
|
|||||||
<i className="ri-edit-line"></i>
|
<i className="ri-edit-line"></i>
|
||||||
修改
|
修改
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
|
{/* 下载按钮 - 需要 document:document:view 权限 */}
|
||||||
|
{canView && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
||||||
@@ -1439,7 +1468,9 @@ export default function DocumentsIndex() {
|
|||||||
<i className="ri-download-line"></i>
|
<i className="ri-download-line"></i>
|
||||||
下载
|
下载
|
||||||
</button>
|
</button>
|
||||||
{record.type === '1' && record.fileStatus === 'Processed' && (
|
)}
|
||||||
|
{/* 追加附件和上传模板按钮 - 需要 document:document:view 权限 */}
|
||||||
|
{canView && record.type === '1' && record.fileStatus === 'Processed' && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1469,6 +1500,8 @@ export default function DocumentsIndex() {
|
|||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{/* 删除按钮 - 需要 document:document:view 权限 */}
|
||||||
|
{canView && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="text-xs px-2 py-1 h-7 text-error hover:underline hover:text-red-700"
|
className="text-xs px-2 py-1 h-7 text-error hover:underline hover:text-red-700"
|
||||||
@@ -1477,6 +1510,7 @@ export default function DocumentsIndex() {
|
|||||||
<i className="ri-delete-bin-line"></i>
|
<i className="ri-delete-bin-line"></i>
|
||||||
删除
|
删除
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -448,8 +448,8 @@ export default function Home() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 高风险用户 */}
|
{/* 高风险用户 隐藏*/}
|
||||||
{topRiskUsers.available && (
|
{topRiskUsers.available && false &&(
|
||||||
<Card
|
<Card
|
||||||
title="高风险用户 Top 5"
|
title="高风险用户 Top 5"
|
||||||
icon="ri-shield-user-line"
|
icon="ri-shield-user-line"
|
||||||
|
|||||||
@@ -0,0 +1,891 @@
|
|||||||
|
# 企查查模块 API 接口文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
企查查模块提供企业工商信息和失信核查查询功能,数据来源于企查查API。
|
||||||
|
|
||||||
|
### 基础信息
|
||||||
|
|
||||||
|
|项目 |说明 |
|
||||||
|
|---|---|
|
||||||
|
|基础路径 |`/api/v2/qichacha` |
|
||||||
|
|认证方式 |Bearer Token(JWT) |
|
||||||
|
|数据格式 |JSON |
|
||||||
|
|
||||||
|
### 费用说明
|
||||||
|
|
||||||
|
|接口 |企查查接口 |单价 |
|
||||||
|
|---|---|---|
|
||||||
|
|工商信息 |410接口 |0.20元/次 |
|
||||||
|
|失信核查 |740接口 |0.30元/次 |
|
||||||
|
|完整查询 |410+740 |0.50元/次 |
|
||||||
|
|
||||||
|
> **注意**:仅调用企查查API时产生费用,使用数据库记录不产生费用。
|
||||||
|
|
||||||
|
### 权限要求
|
||||||
|
|
||||||
|
|权限键 |说明 |分配角色 |
|
||||||
|
|---|---|---|
|
||||||
|
|`qichacha:company:query` |查询企业信息 |市级管理员、省级管理员、普通员工 |
|
||||||
|
|`qichacha:status:read` |查看记录状态 |市级管理员、省级管理员、普通员工 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 业务逻辑
|
||||||
|
|
||||||
|
### 核心流程
|
||||||
|
|
||||||
|
企查查模块的核心设计理念是**数据库优先**,通过本地数据库存储已查询过的企业信息,减少对企查查API的调用次数,从而节省费用。
|
||||||
|
|
||||||
|
#### 查询流程图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 用户发起查询请求 │
|
||||||
|
│ keyword = "腾讯科技" 或 信用代码 │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌────────────────────────┐
|
||||||
|
│ 1. 查询数据库 │
|
||||||
|
│ (三字段OR匹配) │
|
||||||
|
│ search_key = keyword │
|
||||||
|
│ OR credit_code = keyword │
|
||||||
|
│ OR company_name = keyword │
|
||||||
|
└────────────────────────┘
|
||||||
|
│
|
||||||
|
┌────────────┴────────────┐
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌──────────┐ ┌──────────┐
|
||||||
|
│ 无记录 │ │ 有记录 │
|
||||||
|
└──────────┘ └──────────┘
|
||||||
|
│ │
|
||||||
|
│ ▼
|
||||||
|
│ ┌─────────────────────────┐
|
||||||
|
│ │ 2. 检查force_refresh │
|
||||||
|
│ │ 参数是否为true? │
|
||||||
|
│ └─────────────────────────┘
|
||||||
|
│ │ │
|
||||||
|
│ true false
|
||||||
|
│ │ │
|
||||||
|
│ │ ▼
|
||||||
|
│ │ ┌──────────────────┐
|
||||||
|
│ │ │ 3. 检查数据新鲜度 │
|
||||||
|
│ │ │ updated_at距今 │
|
||||||
|
│ │ │ 是否超过30天? │
|
||||||
|
│ │ └──────────────────┘
|
||||||
|
│ │ │ │
|
||||||
|
│ │ >=30天 <30天
|
||||||
|
│ │ │ │
|
||||||
|
▼ ▼ ▼ ▼
|
||||||
|
┌─────────────────────────────┐ ┌──────────────┐
|
||||||
|
│ 4. 调用企查查API │ │ 5. 直接返回 │
|
||||||
|
│ ├─ 410接口(工商) 0.20元 │ │ 数据库记录 │
|
||||||
|
│ └─ 740接口(失信) 0.30元 │ │ │
|
||||||
|
│ │ │ ✓ 不产生费用 │
|
||||||
|
│ 5. 写入/更新数据库 │ │ ✓ 响应更快 │
|
||||||
|
│ ├─ search_key = keyword │ └──────────────┘
|
||||||
|
│ ├─ credit_code (从API获取) │
|
||||||
|
│ ├─ company_name (从API获取)│
|
||||||
|
│ ├─ enterprise = JSON │
|
||||||
|
│ └─ dishonesty = JSON │
|
||||||
|
│ │
|
||||||
|
│ 6. 返回查询结果 │
|
||||||
|
│ ✗ 产生费用: 0.50元/次 │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据刷新策略
|
||||||
|
|
||||||
|
|场景 |条件 |动作 |是否产生费用 |
|
||||||
|
|---|---|---|---|
|
||||||
|
|首次查询 |数据库无此企业记录 |调用API → 写入数据库 |✓ 0.50元 |
|
||||||
|
|强制刷新 |`force_refresh=true` |调用API → 更新数据库 |✓ 0.50元 |
|
||||||
|
|数据过期 |记录存在但超过30天 |调用API → 更新数据库 |✓ 0.50元 |
|
||||||
|
|数据有效 |记录存在且未超30天 |直接返回数据库记录 |✗ 免费 |
|
||||||
|
|
||||||
|
### 三字段匹配机制
|
||||||
|
|
||||||
|
数据库查询使用**OR逻辑**匹配以下三个字段:
|
||||||
|
|
||||||
|
|字段 |说明 |示例 |
|
||||||
|
|---|---|---|
|
||||||
|
|`search_key` |原始查询关键词 |"腾讯科技" |
|
||||||
|
|`credit_code` |统一社会信用代码 |"91440300708461136T" |
|
||||||
|
|`company_name` |企业全称(从API获取) |"腾讯科技(深圳)有限公司" |
|
||||||
|
|
||||||
|
**匹配示例:**
|
||||||
|
|
||||||
|
|
||||||
|
1. 用户首次用 `"腾讯科技"` 查询
|
||||||
|
- 数据库无记录 → 调用API
|
||||||
|
- API返回企业全称和信用代码
|
||||||
|
- 写入数据库:`search_key="腾讯科技"`, `credit_code="91440300708461136T"`, `company_name="腾讯科技(深圳)有限公司"`
|
||||||
|
2. 用户再用 `"91440300708461136T"` 查询
|
||||||
|
- 数据库查询:`WHERE search_key='91440300708461136T' OR credit_code='91440300708461136T' OR company_name='91440300708461136T'`
|
||||||
|
- 命中 `credit_code` 字段 → 返回已有记录(不调用API)
|
||||||
|
3. 用户再用 `"腾讯科技(深圳)有限公司"` 查询
|
||||||
|
- 命中 `company_name` 字段 → 返回已有记录(不调用API)
|
||||||
|
|
||||||
|
### 数据存储结构
|
||||||
|
|
||||||
|
数据库表:`qcc_company_info`
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|id |SERIAL |主键 |
|
||||||
|
|search_key |VARCHAR(500) |查询关键词(唯一索引) |
|
||||||
|
|credit_code |VARCHAR(50) |统一社会信用代码 |
|
||||||
|
|company_name |VARCHAR(500) |企业名称 |
|
||||||
|
|enterprise |JSONB |410接口原始返回(工商信息) |
|
||||||
|
|dishonesty |JSONB |740接口原始返回(失信信息) |
|
||||||
|
|created_at |TIMESTAMPTZ |创建时间 |
|
||||||
|
|updated_at |TIMESTAMPTZ |更新时间(用于判断数据新鲜度) |
|
||||||
|
|
||||||
|
### API调用说明
|
||||||
|
|
||||||
|
#### 企查查接口
|
||||||
|
|
||||||
|
|接口代码 |接口名称 |请求路径 |单价 |
|
||||||
|
|---|---|---|---|
|
||||||
|
|410 |企业工商信息 |`/ECIV4/GetBasicDetailsByName` |0.20元/次 |
|
||||||
|
|740 |失信核查 |`/ShixinCheck/GetList` |0.30元/次 |
|
||||||
|
|
||||||
|
#### Token生成规则
|
||||||
|
|
||||||
|
```
|
||||||
|
Token = MD5(AppKey + Timespan + SecretKey).toUpperCase()
|
||||||
|
```
|
||||||
|
|
||||||
|
- `AppKey`: 企查查分配的应用Key
|
||||||
|
- `Timespan`: Unix时间戳(秒)
|
||||||
|
- `SecretKey`: 企查查分配的密钥
|
||||||
|
|
||||||
|
#### 请求头
|
||||||
|
|
||||||
|
|Header |值 |
|
||||||
|
|---|---|
|
||||||
|
|Token |MD5签名(大写) |
|
||||||
|
|Timespan |Unix时间戳 |
|
||||||
|
|Content-Type |application/json |
|
||||||
|
|
||||||
|
### 并发处理
|
||||||
|
|
||||||
|
查询完整企业信息时,系统会**并发调用**410和740两个接口:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 并发请求示例
|
||||||
|
enterprise_task = client.get_enterprise_info(keyword) # 410接口
|
||||||
|
dishonesty_task = client.get_dishonesty_info(keyword) # 740接口
|
||||||
|
|
||||||
|
enterprise_resp, dishonesty_resp = await asyncio.gather(
|
||||||
|
enterprise_task,
|
||||||
|
dishonesty_task,
|
||||||
|
return_exceptions=True
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 减少总响应时间(两个请求并行执行)
|
||||||
|
- 单个接口失败不影响另一个接口的结果
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API 接口
|
||||||
|
|
||||||
|
### 1. 查询企业信息
|
||||||
|
|
||||||
|
查询企业完整信息(工商信息 + 失信核查)
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v2/qichacha/company
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求参数**
|
||||||
|
|
||||||
|
|字段 |类型 |必填 |说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
|keyword |string |是 |查询关键词(企业名称或统一社会信用代码),2-200字符 |
|
||||||
|
|force_refresh |boolean |否 |是否强制刷新,默认false |
|
||||||
|
|
||||||
|
**请求示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"keyword": "腾讯科技(深圳)有限公司",
|
||||||
|
"force_refresh": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应参数**
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|success |boolean |查询是否成功 |
|
||||||
|
|message |string |响应消息 |
|
||||||
|
|data |object |企业信息(见下表) |
|
||||||
|
|error_code |string |错误码(失败时返回) |
|
||||||
|
|error_details |object |错误详情(失败时返回) |
|
||||||
|
|
||||||
|
**data 字段说明**
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|search_key |string |查询关键词 |
|
||||||
|
|credit_code |string |统一社会信用代码 |
|
||||||
|
|company_name |string |企业名称 |
|
||||||
|
|enterprise |object |企业工商信息(410接口原始返回) |
|
||||||
|
|dishonesty |object |失信核查信息(740接口原始返回) |
|
||||||
|
|has_dishonesty |boolean |是否有失信记录 |
|
||||||
|
|dishonesty_count |integer |失信记录数量 |
|
||||||
|
|updated_at |datetime |数据更新时间 |
|
||||||
|
|
||||||
|
**enterprise 字段说明(410接口返回)**
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|KeyNo |string |企查查内部KeyNo |
|
||||||
|
|Name |string |企业名称 |
|
||||||
|
|No |string |工商注册号 |
|
||||||
|
|BelongOrg |string |登记机关 |
|
||||||
|
|OperId |string |法定代表人ID |
|
||||||
|
|OperName |string |法定代表人姓名 |
|
||||||
|
|DesignatedRepresentativeList |array |指定代表人列表 |
|
||||||
|
|StartDate |string |成立日期(格式:YYYY-MM-DD HH:mm:ss) |
|
||||||
|
|EndDate |string |注销日期(空字符串表示未注销) |
|
||||||
|
|Status |string |企业状态(存续、注销、吊销等) |
|
||||||
|
|Province |string |省份代码(如JS=江苏) |
|
||||||
|
|UpdatedDate |string |数据更新日期 |
|
||||||
|
|CreditCode |string |统一社会信用代码(部分老企业可能为空) |
|
||||||
|
|RegistCapi |string |注册资本(含单位,如"36225万元") |
|
||||||
|
|RegisteredCapital |string |注册资本数值 |
|
||||||
|
|RegisteredCapitalUnit |string |注册资本单位(如"万") |
|
||||||
|
|RegisteredCapitalCCY |string |注册资本币种(如"CNY") |
|
||||||
|
|EconKind |string |企业类型(如"有限责任公司"、"全民所有制"等) |
|
||||||
|
|Address |string |注册地址 |
|
||||||
|
|Scope |string |经营范围 |
|
||||||
|
|TermStart |string |营业期限起始日期 |
|
||||||
|
|TermEnd |string |营业期限终止日期 |
|
||||||
|
|CheckDate |string |核准日期 |
|
||||||
|
|OrgNo |string |组织机构代码 |
|
||||||
|
|IsOnStock |string |是否上市("0"=否,"1"=是) |
|
||||||
|
|StockNumber |string |股票代码(非上市为null) |
|
||||||
|
|StockType |string |股票类型(非上市为null) |
|
||||||
|
|OriginalName |array |曾用名列表 |
|
||||||
|
|ImageUrl |string |企业Logo图片URL |
|
||||||
|
|EntType |string |企业类型代码 |
|
||||||
|
|RecCap |string |实缴资本(含单位) |
|
||||||
|
|PaidUpCapital |string |实缴资本数值 |
|
||||||
|
|PaidUpCapitalUnit |string |实缴资本单位 |
|
||||||
|
|PaidUpCapitalCCY |string |实缴资本币种 |
|
||||||
|
|RevokeInfo |object |注销/吊销信息 |
|
||||||
|
|Area |object |地区信息 |
|
||||||
|
|AreaCode |string |地区代码 |
|
||||||
|
|
||||||
|
**enterprise.DesignatedRepresentativeList[] 指定代表人**
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|PartnerName |string |股东名称 |
|
||||||
|
|DelegatedName |string |委派代表姓名 |
|
||||||
|
|
||||||
|
**enterprise.OriginalName[] 曾用名**
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|Name |string |曾用名称 |
|
||||||
|
|ChangeDate |string |变更日期 |
|
||||||
|
|
||||||
|
**enterprise.RevokeInfo 注销/吊销信息**
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|CancelDate |string |注销日期 |
|
||||||
|
|CancelReason |string |注销原因 |
|
||||||
|
|RevokeDate |string |吊销日期 |
|
||||||
|
|RevokeReason |string |吊销原因 |
|
||||||
|
|
||||||
|
**enterprise.Area 地区信息**
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|Province |string |省份 |
|
||||||
|
|City |string |城市 |
|
||||||
|
|County |string |区县 |
|
||||||
|
|
||||||
|
**dishonesty 字段说明(740接口返回)**
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|VerifyResult |integer |核查结果:0-无失信记录,1-有失信记录 |
|
||||||
|
|Data |array |失信记录列表(VerifyResult=0时为null或空数组) |
|
||||||
|
|
||||||
|
**dishonesty.Data[] 失信记录字段说明**
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|Id |string |记录唯一标识 |
|
||||||
|
|Liandate |string |立案日期(格式:YYYY-MM-DD) |
|
||||||
|
|Anno |string |案号 |
|
||||||
|
|Executegov |string |执行法院 |
|
||||||
|
|Executestatus |string |执行情况(全部未履行/部分履行/全部履行) |
|
||||||
|
|Publicdate |string |发布日期(格式:YYYY-MM-DD) |
|
||||||
|
|Executeno |string |执行依据文号 |
|
||||||
|
|ActionRemark |string |失信被执行人行为具体情形 |
|
||||||
|
|Amount |string |执行标的金额(元) |
|
||||||
|
|
||||||
|
**响应示例(成功)**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "查询成功",
|
||||||
|
"data": {
|
||||||
|
"search_key": "腾讯科技(深圳)有限公司",
|
||||||
|
"credit_code": "91440300708461136T",
|
||||||
|
"company_name": "腾讯科技(深圳)有限公司",
|
||||||
|
"enterprise": {
|
||||||
|
"KeyNo": "p5ef6e1c39f4817d33dfb4e9",
|
||||||
|
"Name": "腾讯科技(深圳)有限公司",
|
||||||
|
"No": "440301103206221",
|
||||||
|
"BelongOrg": "深圳市市场监督管理局",
|
||||||
|
"OperId": "p5ef6e1c39f4817d33dfb4e9op",
|
||||||
|
"OperName": "马化腾",
|
||||||
|
"DesignatedRepresentativeList": [],
|
||||||
|
"StartDate": "2000-02-24 00:00:00",
|
||||||
|
"EndDate": "",
|
||||||
|
"Status": "存续",
|
||||||
|
"Province": "GD",
|
||||||
|
"UpdatedDate": "2024-12-10 15:30:00",
|
||||||
|
"CreditCode": "91440300708461136T",
|
||||||
|
"RegistCapi": "65000万人民币",
|
||||||
|
"RegisteredCapital": "65000",
|
||||||
|
"RegisteredCapitalUnit": "万",
|
||||||
|
"RegisteredCapitalCCY": "CNY",
|
||||||
|
"EconKind": "有限责任公司",
|
||||||
|
"Address": "深圳市南山区粤海街道科技中一路腾讯大厦35层",
|
||||||
|
"Scope": "从事计算机软硬件及周边设备、通讯设备、数码产品、网络设备的技术开发、销售及相关的技术咨询、技术服务;经营进出口业务;从事广告业务;物业管理",
|
||||||
|
"TermStart": "2000-02-24 00:00:00",
|
||||||
|
"TermEnd": "2050-02-24 00:00:00",
|
||||||
|
"CheckDate": "2023-08-15 00:00:00",
|
||||||
|
"OrgNo": "708461136",
|
||||||
|
"IsOnStock": "1",
|
||||||
|
"StockNumber": "00700.HK",
|
||||||
|
"StockType": "港股",
|
||||||
|
"OriginalName": [
|
||||||
|
{
|
||||||
|
"Name": "深圳市腾讯计算机系统有限公司",
|
||||||
|
"ChangeDate": "2000-06-01"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ImageUrl": "https://img.qichacha.com/xxx.png",
|
||||||
|
"EntType": "1",
|
||||||
|
"RecCap": "65000万人民币",
|
||||||
|
"PaidUpCapital": "65000",
|
||||||
|
"PaidUpCapitalUnit": "万",
|
||||||
|
"PaidUpCapitalCCY": "CNY",
|
||||||
|
"RevokeInfo": {
|
||||||
|
"CancelDate": "",
|
||||||
|
"CancelReason": "",
|
||||||
|
"RevokeDate": "",
|
||||||
|
"RevokeReason": ""
|
||||||
|
},
|
||||||
|
"Area": {
|
||||||
|
"Province": "广东省",
|
||||||
|
"City": "深圳市",
|
||||||
|
"County": "南山区"
|
||||||
|
},
|
||||||
|
"AreaCode": "440305"
|
||||||
|
},
|
||||||
|
"dishonesty": {
|
||||||
|
"VerifyResult": 0,
|
||||||
|
"Data": null
|
||||||
|
},
|
||||||
|
"has_dishonesty": false,
|
||||||
|
"dishonesty_count": 0,
|
||||||
|
"updated_at": "2024-12-12T10:30:00+08:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例(有失信记录)**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "查询成功",
|
||||||
|
"data": {
|
||||||
|
"search_key": "某某建设工程有限公司",
|
||||||
|
"credit_code": "91440106MA5CXXXX12",
|
||||||
|
"company_name": "某某建设工程有限公司",
|
||||||
|
"enterprise": {
|
||||||
|
"KeyNo": "abc123def456ghi789jkl",
|
||||||
|
"Name": "某某建设工程有限公司",
|
||||||
|
"No": "440106000123456",
|
||||||
|
"BelongOrg": "广州市天河区市场监督管理局",
|
||||||
|
"OperId": "abc123def456ghi789jklop",
|
||||||
|
"OperName": "张三",
|
||||||
|
"DesignatedRepresentativeList": [],
|
||||||
|
"StartDate": "2018-05-10 00:00:00",
|
||||||
|
"EndDate": "",
|
||||||
|
"Status": "存续",
|
||||||
|
"Province": "GD",
|
||||||
|
"UpdatedDate": "2024-12-01 09:00:00",
|
||||||
|
"CreditCode": "91440106MA5CXXXX12",
|
||||||
|
"RegistCapi": "5000万人民币",
|
||||||
|
"RegisteredCapital": "5000",
|
||||||
|
"RegisteredCapitalUnit": "万",
|
||||||
|
"RegisteredCapitalCCY": "CNY",
|
||||||
|
"EconKind": "有限责任公司(自然人投资或控股)",
|
||||||
|
"Address": "广州市天河区某某路123号",
|
||||||
|
"Scope": "建筑工程施工;市政公用工程施工;房屋建筑工程施工;建筑装饰、装修工程施工;园林绿化工程施工",
|
||||||
|
"TermStart": "2018-05-10 00:00:00",
|
||||||
|
"TermEnd": "2048-05-09 00:00:00",
|
||||||
|
"CheckDate": "2023-06-20 00:00:00",
|
||||||
|
"OrgNo": "MA5CXXXX1",
|
||||||
|
"IsOnStock": "0",
|
||||||
|
"StockNumber": null,
|
||||||
|
"StockType": null,
|
||||||
|
"OriginalName": [],
|
||||||
|
"ImageUrl": "",
|
||||||
|
"EntType": "1",
|
||||||
|
"RecCap": "1000万人民币",
|
||||||
|
"PaidUpCapital": "1000",
|
||||||
|
"PaidUpCapitalUnit": "万",
|
||||||
|
"PaidUpCapitalCCY": "CNY",
|
||||||
|
"RevokeInfo": {
|
||||||
|
"CancelDate": "",
|
||||||
|
"CancelReason": "",
|
||||||
|
"RevokeDate": "",
|
||||||
|
"RevokeReason": ""
|
||||||
|
},
|
||||||
|
"Area": {
|
||||||
|
"Province": "广东省",
|
||||||
|
"City": "广州市",
|
||||||
|
"County": "天河区"
|
||||||
|
},
|
||||||
|
"AreaCode": "440106"
|
||||||
|
},
|
||||||
|
"dishonesty": {
|
||||||
|
"VerifyResult": 1,
|
||||||
|
"Data": [
|
||||||
|
{
|
||||||
|
"Id": "c2edc410f8ceed3ed256055baa8df1432",
|
||||||
|
"Liandate": "2021-08-10",
|
||||||
|
"Anno": "(2021)粤0106执34224号",
|
||||||
|
"Executegov": "广州市天河区人民法院",
|
||||||
|
"Executestatus": "全部未履行",
|
||||||
|
"Publicdate": "2022-01-07",
|
||||||
|
"Executeno": "(2020)粤0106民初12345号",
|
||||||
|
"ActionRemark": "有履行能力而拒不履行生效法律文书确定义务",
|
||||||
|
"Amount": "1098000"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"has_dishonesty": true,
|
||||||
|
"dishonesty_count": 1,
|
||||||
|
"updated_at": "2024-12-12T10:30:00+08:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例(失败)**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "当前KEY已欠费",
|
||||||
|
"data": null,
|
||||||
|
"error_code": "102",
|
||||||
|
"error_details": {
|
||||||
|
"order_number": "20241212103000123",
|
||||||
|
"is_charged": false,
|
||||||
|
"is_valid_request": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 仅查询工商信息
|
||||||
|
|
||||||
|
仅查询企业工商信息(410接口),费用 0.20元/次
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v2/qichacha/enterprise
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求参数**
|
||||||
|
|
||||||
|
同 [查询企业信息](#1-查询企业信息)
|
||||||
|
|
||||||
|
**响应参数**
|
||||||
|
|
||||||
|
同 [查询企业信息](#1-查询企业信息),但 `dishonesty` 字段为 null
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 仅查询失信信息
|
||||||
|
|
||||||
|
仅查询企业失信信息(740接口),费用 0.30元/次
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v2/qichacha/dishonesty
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求参数**
|
||||||
|
|
||||||
|
同 [查询企业信息](#1-查询企业信息)
|
||||||
|
|
||||||
|
**响应参数**
|
||||||
|
|
||||||
|
同 [查询企业信息](#1-查询企业信息),但 `enterprise` 字段为 null
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 批量查询企业信息
|
||||||
|
|
||||||
|
批量查询多个企业信息,单次最多10个
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v2/qichacha/batch
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求参数**
|
||||||
|
|
||||||
|
|字段 |类型 |必填 |说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
|keywords |array[string] |是 |查询关键词列表,1-10个 |
|
||||||
|
|force_refresh |boolean |否 |是否强制刷新,默认false |
|
||||||
|
|
||||||
|
**请求示例**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"keywords": ["腾讯科技", "阿里巴巴", "华为技术"],
|
||||||
|
"force_refresh": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应参数**
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|success |boolean |是否全部成功 |
|
||||||
|
|total |integer |总查询数 |
|
||||||
|
|success_count |integer |成功数 |
|
||||||
|
|failed_count |integer |失败数 |
|
||||||
|
|results |array |查询结果列表(每个元素同单个查询响应) |
|
||||||
|
|
||||||
|
**响应示例**
|
||||||
|
|
||||||
|
> 注:`results` 数组中每个元素的 `data` 结构与单个查询响应的 `data` 字段完全一致,包含完整的 `enterprise` 和 `dishonesty` 对象。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"total": 3,
|
||||||
|
"success_count": 3,
|
||||||
|
"failed_count": 0,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "查询成功",
|
||||||
|
"data": {
|
||||||
|
"search_key": "腾讯科技",
|
||||||
|
"credit_code": "91440300708461136T",
|
||||||
|
"company_name": "腾讯科技(深圳)有限公司",
|
||||||
|
"enterprise": {
|
||||||
|
"KeyNo": "p5ef6e1c39f4817d33dfb4e9",
|
||||||
|
"Name": "腾讯科技(深圳)有限公司",
|
||||||
|
"No": "440301103206221",
|
||||||
|
"BelongOrg": "深圳市市场监督管理局",
|
||||||
|
"OperId": "p5ef6e1c39f4817d33dfb4e9op",
|
||||||
|
"OperName": "马化腾",
|
||||||
|
"DesignatedRepresentativeList": [],
|
||||||
|
"StartDate": "2000-02-24 00:00:00",
|
||||||
|
"EndDate": "",
|
||||||
|
"Status": "存续",
|
||||||
|
"Province": "GD",
|
||||||
|
"UpdatedDate": "2024-12-10 15:30:00",
|
||||||
|
"CreditCode": "91440300708461136T",
|
||||||
|
"RegistCapi": "65000万人民币",
|
||||||
|
"RegisteredCapital": "65000",
|
||||||
|
"RegisteredCapitalUnit": "万",
|
||||||
|
"RegisteredCapitalCCY": "CNY",
|
||||||
|
"EconKind": "有限责任公司",
|
||||||
|
"Address": "深圳市南山区粤海街道科技中一路腾讯大厦35层",
|
||||||
|
"Scope": "从事计算机软硬件及周边设备、通讯设备、数码产品、网络设备的技术开发、销售及相关的技术咨询、技术服务;经营进出口业务;从事广告业务;物业管理",
|
||||||
|
"TermStart": "2000-02-24 00:00:00",
|
||||||
|
"TermEnd": "2050-02-24 00:00:00",
|
||||||
|
"CheckDate": "2023-08-15 00:00:00",
|
||||||
|
"OrgNo": "708461136",
|
||||||
|
"IsOnStock": "1",
|
||||||
|
"StockNumber": "00700.HK",
|
||||||
|
"StockType": "港股",
|
||||||
|
"OriginalName": [],
|
||||||
|
"ImageUrl": "https://img.qichacha.com/xxx.png",
|
||||||
|
"EntType": "1",
|
||||||
|
"RecCap": "65000万人民币",
|
||||||
|
"PaidUpCapital": "65000",
|
||||||
|
"PaidUpCapitalUnit": "万",
|
||||||
|
"PaidUpCapitalCCY": "CNY",
|
||||||
|
"RevokeInfo": {
|
||||||
|
"CancelDate": "",
|
||||||
|
"CancelReason": "",
|
||||||
|
"RevokeDate": "",
|
||||||
|
"RevokeReason": ""
|
||||||
|
},
|
||||||
|
"Area": {
|
||||||
|
"Province": "广东省",
|
||||||
|
"City": "深圳市",
|
||||||
|
"County": "南山区"
|
||||||
|
},
|
||||||
|
"AreaCode": "440305"
|
||||||
|
},
|
||||||
|
"dishonesty": {
|
||||||
|
"VerifyResult": 0,
|
||||||
|
"Data": null
|
||||||
|
},
|
||||||
|
"has_dishonesty": false,
|
||||||
|
"dishonesty_count": 0,
|
||||||
|
"updated_at": "2024-12-12T10:30:00+08:00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "查询成功",
|
||||||
|
"data": {
|
||||||
|
"search_key": "阿里巴巴",
|
||||||
|
"credit_code": "91330100799655058B",
|
||||||
|
"company_name": "阿里巴巴(中国)有限公司",
|
||||||
|
"enterprise": { "/* 完整enterprise对象,结构同上 */" : "..." },
|
||||||
|
"dishonesty": { "VerifyResult": 0, "Data": null },
|
||||||
|
"has_dishonesty": false,
|
||||||
|
"dishonesty_count": 0,
|
||||||
|
"updated_at": "2024-12-12T10:31:00+08:00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "查询成功",
|
||||||
|
"data": {
|
||||||
|
"search_key": "华为技术",
|
||||||
|
"credit_code": "914403006194776951",
|
||||||
|
"company_name": "华为技术有限公司",
|
||||||
|
"enterprise": { "/* 完整enterprise对象,结构同上 */" : "..." },
|
||||||
|
"dishonesty": { "VerifyResult": 0, "Data": null },
|
||||||
|
"has_dishonesty": false,
|
||||||
|
"dishonesty_count": 0,
|
||||||
|
"updated_at": "2024-12-12T10:32:00+08:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 查询数据库记录状态
|
||||||
|
|
||||||
|
查询企业在数据库中的记录状态(不调用企查查API,不产生费用)
|
||||||
|
|
||||||
|
**请求**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v2/qichacha/status?keyword=腾讯科技
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求参数**
|
||||||
|
|
||||||
|
|字段 |类型 |必填 |说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
|keyword |string |是 |查询关键词,最少2字符 |
|
||||||
|
|
||||||
|
**响应参数**
|
||||||
|
|
||||||
|
|字段 |类型 |说明 |
|
||||||
|
|---|---|---|
|
||||||
|
|exists |boolean |记录是否存在 |
|
||||||
|
|keyword |string |查询关键词 |
|
||||||
|
|search_key |string |数据库中的search_key |
|
||||||
|
|credit_code |string |统一社会信用代码 |
|
||||||
|
|company_name |string |企业名称 |
|
||||||
|
|has_enterprise |boolean |是否有工商信息 |
|
||||||
|
|has_dishonesty |boolean |是否有失信信息 |
|
||||||
|
|updated_at |datetime |数据更新时间 |
|
||||||
|
|age_days |integer |数据已存在天数 |
|
||||||
|
|refresh_threshold_days |integer |刷新阈值(默认30天) |
|
||||||
|
|need_refresh |boolean |是否需要刷新 |
|
||||||
|
|
||||||
|
**响应示例(有记录)**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"exists": true,
|
||||||
|
"keyword": "腾讯科技",
|
||||||
|
"search_key": "腾讯科技(深圳)有限公司",
|
||||||
|
"credit_code": "91440300708461136T",
|
||||||
|
"company_name": "腾讯科技(深圳)有限公司",
|
||||||
|
"has_enterprise": true,
|
||||||
|
"has_dishonesty": true,
|
||||||
|
"updated_at": "2024-12-01T10:30:00+08:00",
|
||||||
|
"age_days": 11,
|
||||||
|
"refresh_threshold_days": 30,
|
||||||
|
"need_refresh": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例(无记录)**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"exists": false,
|
||||||
|
"keyword": "某某不存在的公司"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 错误码
|
||||||
|
|
||||||
|
### 企查查API状态码
|
||||||
|
|
||||||
|
#### 有效请求(会计费)
|
||||||
|
|
||||||
|
|状态码 |说明 |
|
||||||
|
|---|---|
|
||||||
|
|200 |查询成功 |
|
||||||
|
|201 |查询无结果 |
|
||||||
|
|202 |查询参数错误 |
|
||||||
|
|205 |等待处理中 |
|
||||||
|
|207 |请求数据条目数超过上限 |
|
||||||
|
|208 |此接口不支持此公司类型查询 |
|
||||||
|
|209 |企业数量超过上限 |
|
||||||
|
|213 |参数长度不能小于2 |
|
||||||
|
|215 |不支持的查询关键字 |
|
||||||
|
|
||||||
|
#### 无效请求(不计费)
|
||||||
|
|
||||||
|
|状态码 |说明 |
|
||||||
|
|---|---|
|
||||||
|
|101 |当前的KEY无效或还未生效 |
|
||||||
|
|102 |当前KEY已欠费 |
|
||||||
|
|103 |当前KEY被暂停使用 |
|
||||||
|
|107 |被禁止的IP或签名错误 |
|
||||||
|
|111 |接口权限未开通 |
|
||||||
|
|112 |账号剩余使用量不足或已过期 |
|
||||||
|
|
||||||
|
### HTTP状态码
|
||||||
|
|
||||||
|
|状态码 |说明 |
|
||||||
|
|---|---|
|
||||||
|
|200 |成功 |
|
||||||
|
|401 |未认证(Token无效或过期) |
|
||||||
|
|403 |无权限(缺少qichacha:company:query权限) |
|
||||||
|
|500 |服务器内部错误 |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
在环境配置文件中添加:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# 企查查API配置
|
||||||
|
QCC_APP_KEY=你的AppKey
|
||||||
|
QCC_SECRET_KEY=你的SecretKey
|
||||||
|
|
||||||
|
# 可选配置
|
||||||
|
QCC_BASE_URL=https://api.qichacha.com
|
||||||
|
QCC_TIMEOUT=30
|
||||||
|
QCC_CACHE_DAYS=30
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### cURL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查询企业信息
|
||||||
|
curl -X POST "http://localhost:8000/api/v2/qichacha/company" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer <your_token>" \
|
||||||
|
-d '{"keyword": "腾讯科技", "force_refresh": false}'
|
||||||
|
|
||||||
|
# 批量查询
|
||||||
|
curl -X POST "http://localhost:8000/api/v2/qichacha/batch" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer <your_token>" \
|
||||||
|
-d '{"keywords": ["腾讯", "阿里", "华为"]}'
|
||||||
|
|
||||||
|
# 查询记录状态
|
||||||
|
curl -X GET "http://localhost:8000/api/v2/qichacha/status?keyword=腾讯" \
|
||||||
|
-H "Authorization: Bearer <your_token>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python
|
||||||
|
|
||||||
|
```python
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
async def query_company(keyword: str, token: str):
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
"http://localhost:8000/api/v2/qichacha/company",
|
||||||
|
json={"keyword": keyword, "force_refresh": False},
|
||||||
|
headers={"Authorization": f"Bearer {token}"}
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function queryCompany(keyword, token) {
|
||||||
|
const response = await fetch('/api/v2/qichacha/company', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
keyword: keyword,
|
||||||
|
force_refresh: false
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
# 企业工商信息查询接口文档
|
||||||
|
|
||||||
|
## 接口概述
|
||||||
|
|
||||||
|
该接口用于查询企业工商注册信息,返回企业的基本信息、注册资本、经营范围、法定代表人等详细数据。
|
||||||
|
|
||||||
|
## 响应结构
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 描述 |
|
||||||
|
|--------|------|------|
|
||||||
|
| Result | Object | 查询结果数据 |
|
||||||
|
| Status | String | 状态码(200 表示成功) |
|
||||||
|
| Message | String | 响应消息 |
|
||||||
|
| OrderNumber | String | 订单编号 |
|
||||||
|
|
||||||
|
## Result 字段说明
|
||||||
|
|
||||||
|
### 基本信息
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 长度 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| KeyNo | String | 100 | 主键 |
|
||||||
|
| Name | String | 1000 | 企业名称 |
|
||||||
|
| No | String | 200 | 根据企业性质返回不同值(对中国境内企业(EntType=0/1/4/7/9/10/11)返回工商注册号,对中国香港企业返回企业编号,对中国台湾企业返回企业编号) |
|
||||||
|
| BelongOrg | String | 500 | 登记机关 |
|
||||||
|
| OperId | String | 100 | 法定代表人ID |
|
||||||
|
| OperName | String | 1000 | 法定代表人名称 |
|
||||||
|
| StartDate | String | 50 | 成立日期 |
|
||||||
|
| EndDate | String | 50 | 吊销日期(保留字段) |
|
||||||
|
| Status | String | 100 | 登记状态 |
|
||||||
|
| Province | String | 32 | 省份(缩写,如 JS 表示江苏) |
|
||||||
|
| UpdatedDate | String | 50 | 更新日期 |
|
||||||
|
| CreditCode | String | 50 | 根据企业性质返回不同值(对中国境内企业(EntType=0/1/4/7/9/10/11)返回统一社会信用代码,对中国香港企业返回商业登记号码) |
|
||||||
|
|
||||||
|
### 注册资本信息
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 长度 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| RegistCapi | String | 50 | 注册资本(含单位,如 "36225万元") |
|
||||||
|
| RegisteredCapital | String | 50 | 注册资本数额 |
|
||||||
|
| RegisteredCapitalUnit | String | 50 | 注册资本单位 |
|
||||||
|
| RegisteredCapitalCCY | String | 50 | 注册资本币种(如 CNY) |
|
||||||
|
| RecCap | String | 50 | 实缴资本(含单位) |
|
||||||
|
| PaidUpCapital | String | 50 | 实缴出资额数额 |
|
||||||
|
| PaidUpCapitalUnit | String | 50 | 实缴出资额单位 |
|
||||||
|
| PaidUpCapitalCCY | String | 50 | 实缴出资额币种 |
|
||||||
|
|
||||||
|
### 经营信息
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 长度 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| EconKind | String | 100 | 企业类型(如 "全民所有制") |
|
||||||
|
| Address | String | 1000 | 注册地址 |
|
||||||
|
| Scope | String | 5000 | 经营范围 |
|
||||||
|
| TermStart | String | 50 | 营业期限始 |
|
||||||
|
| TermEnd | String | 50 | 营业期限至 |
|
||||||
|
| CheckDate | String | 50 | 核准日期 |
|
||||||
|
| OrgNo | String | 50 | 组织机构代码 |
|
||||||
|
|
||||||
|
### 上市信息
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 长度 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| IsOnStock | String | 5 | 是否上市(0-未上市,1-上市) |
|
||||||
|
| StockNumber | String | 32 | 股票代码(若A股和港股同时存在,优先返回A股代码) |
|
||||||
|
| StockType | String | 10 | 上市类型(A股、港股、美股、新三板、新四板) |
|
||||||
|
|
||||||
|
### 其他信息
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 长度 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| ImageUrl | String | 500 | 企业Logo地址 |
|
||||||
|
| EntType | String | 10 | 企业性质 |
|
||||||
|
| AreaCode | String | 20 | 行政区划代码 |
|
||||||
|
|
||||||
|
**EntType 企业性质枚举值:**
|
||||||
|
|
||||||
|
| 值 | 描述 |
|
||||||
|
|----|------|
|
||||||
|
| 0 | 大陆企业 |
|
||||||
|
| 1 | 社会组织 |
|
||||||
|
| 4 | 事业单位 |
|
||||||
|
| 7 | 医院 |
|
||||||
|
| 9 | 律师事务所 |
|
||||||
|
| 10 | 学校 |
|
||||||
|
| 11 | 机关单位 |
|
||||||
|
| -1 | 其他 |
|
||||||
|
|
||||||
|
### 嵌套对象
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 描述 |
|
||||||
|
|--------|------|------|
|
||||||
|
| DesignatedRepresentativeList | List\<Object\> | 委派代表列表 |
|
||||||
|
| OriginalName | List\<Object\> | 曾用名列表 |
|
||||||
|
| RevokeInfo | Object | 注销吊销信息 |
|
||||||
|
| Area | Object | 行政区域 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 嵌套对象字段说明
|
||||||
|
|
||||||
|
### DesignatedRepresentativeList(委派代表)
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 长度 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| PartnerName | String | 1000 | 合伙人名称 |
|
||||||
|
| DelegatedName | String | 1000 | 委派代表名称 |
|
||||||
|
|
||||||
|
### OriginalName(曾用名)
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 长度 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| Name | String | 1000 | 曾用名 |
|
||||||
|
| ChangeDate | String | 50 | 变更日期 |
|
||||||
|
|
||||||
|
### RevokeInfo(注销吊销信息)
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 长度 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| CancelDate | String | 50 | 注销日期(如 "2022-01-01") |
|
||||||
|
| CancelReason | String | 2000 | 注销原因 |
|
||||||
|
| RevokeDate | String | 50 | 吊销日期 |
|
||||||
|
| RevokeReason | String | 2000 | 吊销原因 |
|
||||||
|
|
||||||
|
### Area(行政区域)
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 长度 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| Province | String | 32 | 省份 |
|
||||||
|
| City | String | 32 | 城市 |
|
||||||
|
| County | String | 32 | 区域 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Result": {
|
||||||
|
"KeyNo": "xxxxxxxxxxxx",
|
||||||
|
"Name": "xxxxxxx公司",
|
||||||
|
"No": "xxxxxxxxxxxxxxx",
|
||||||
|
"BelongOrg": "xxxxxxxxxxxx管理局",
|
||||||
|
"OperId": "xxxxxxxxxxxxx",
|
||||||
|
"OperName": "xxx",
|
||||||
|
"DesignatedRepresentativeList": [
|
||||||
|
{
|
||||||
|
"PartnerName": "xxx股权投资有限公司",
|
||||||
|
"DelegatedName": "xxx"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"StartDate": "1994-03-04 00:00:00",
|
||||||
|
"EndDate": "",
|
||||||
|
"Status": "注销",
|
||||||
|
"Province": "JS",
|
||||||
|
"UpdatedDate": "2021-12-17 12:34:53",
|
||||||
|
"CreditCode": "",
|
||||||
|
"RegistCapi": "36225万元",
|
||||||
|
"RegisteredCapital": "36225",
|
||||||
|
"RegisteredCapitalUnit": "万",
|
||||||
|
"RegisteredCapitalCCY": "CNY",
|
||||||
|
"EconKind": "全民所有制",
|
||||||
|
"Address": "xxx",
|
||||||
|
"Scope": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"TermStart": "1994-03-04 00:00:00",
|
||||||
|
"TermEnd": "2029-06-28 00:00:00",
|
||||||
|
"CheckDate": "2018-06-28 00:00:00",
|
||||||
|
"OrgNo": "0881xxxx-7",
|
||||||
|
"IsOnStock": "0",
|
||||||
|
"StockNumber": null,
|
||||||
|
"StockType": null,
|
||||||
|
"OriginalName": [
|
||||||
|
{
|
||||||
|
"Name": "xxxxx有限公司",
|
||||||
|
"ChangeDate": "2023-07-06"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ImageUrl": "xxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"EntType": "0",
|
||||||
|
"RecCap": "36225万元",
|
||||||
|
"PaidUpCapital": "36225",
|
||||||
|
"PaidUpCapitalUnit": "万",
|
||||||
|
"PaidUpCapitalCCY": "CNY",
|
||||||
|
"RevokeInfo": {
|
||||||
|
"CancelDate": "2018-06-28",
|
||||||
|
"CancelReason": "其他原因",
|
||||||
|
"RevokeDate": "",
|
||||||
|
"RevokeReason": ""
|
||||||
|
},
|
||||||
|
"Area": {
|
||||||
|
"Province": "江苏省",
|
||||||
|
"City": "苏州市",
|
||||||
|
"County": "苏州工业园区"
|
||||||
|
},
|
||||||
|
"AreaCode": "320571"
|
||||||
|
},
|
||||||
|
"Status": "200",
|
||||||
|
"Message": "【有效请求】查询成功",
|
||||||
|
"OrderNumber": "ECI2021123010294871571463"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **日期格式**:部分日期字段返回格式为 `YYYY-MM-DD HH:mm:ss`,部分为 `YYYY-MM-DD`
|
||||||
|
2. **空值处理**:字段值可能为空字符串 `""` 或 `null`,需做兼容处理
|
||||||
|
3. **企业性质**:根据 `EntType` 字段判断企业类型,不同类型的 `No` 和 `CreditCode` 含义不同
|
||||||
|
4. **上市信息**:仅当 `IsOnStock` 为 `"1"` 时,`StockNumber` 和 `StockType` 才有值
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# 失信核查接口文档
|
||||||
|
|
||||||
|
## 接口概述
|
||||||
|
|
||||||
|
该接口用于查询企业或个人的失信被执行人信息,返回失信记录详情,包括案号、执行法院、履行情况、涉案金额等。
|
||||||
|
|
||||||
|
## 响应结构
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 描述 |
|
||||||
|
|--------|------|------|
|
||||||
|
| Paging | Object | 分页信息 |
|
||||||
|
| Result | Object | 查询结果数据 |
|
||||||
|
| Status | String | 状态码(200 表示成功) |
|
||||||
|
| Message | String | 响应消息 |
|
||||||
|
| OrderNumber | String | 订单编号 |
|
||||||
|
|
||||||
|
## Paging(分页信息)
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 描述 |
|
||||||
|
|--------|------|------|
|
||||||
|
| PageSize | Int32 | 每页记录数 |
|
||||||
|
| PageIndex | Int32 | 当前页码 |
|
||||||
|
| TotalRecords | Int32 | 总记录数 |
|
||||||
|
|
||||||
|
## Result(返回参数)
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 长度 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| VerifyResult | Int32 | 1 | 数据是否存在(1-存在,0-不存在) |
|
||||||
|
| Data | List\<Object\> | - | 数据信息列表 |
|
||||||
|
|
||||||
|
**VerifyResult 枚举值:**
|
||||||
|
|
||||||
|
| 值 | 描述 |
|
||||||
|
|----|------|
|
||||||
|
| 1 | 存在失信记录 |
|
||||||
|
| 0 | 不存在失信记录 |
|
||||||
|
|
||||||
|
## Data(数据信息)
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 长度 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| Id | String | 100 | 主键 |
|
||||||
|
| Liandate | String | 50 | 立案日期(如 "2022-01-01") |
|
||||||
|
| Anno | String | 1000 | 案号 |
|
||||||
|
| Executegov | String | 500 | 执行法院 |
|
||||||
|
| Executestatus | String | 5000 | 被执行人的履行情况 |
|
||||||
|
| Publicdate | String | 50 | 发布日期(如 "2022-01-01") |
|
||||||
|
| Executeno | String | 1000 | 执行依据文号 |
|
||||||
|
| ActionRemark | String | 5000 | 失信行为 |
|
||||||
|
| Amount | String | 50 | 涉案金额(元) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Paging": {
|
||||||
|
"PageSize": 1,
|
||||||
|
"PageIndex": 1,
|
||||||
|
"TotalRecords": 35
|
||||||
|
},
|
||||||
|
"Result": {
|
||||||
|
"VerifyResult": 1,
|
||||||
|
"Data": [
|
||||||
|
{
|
||||||
|
"Id": "c2edc410f8ceed3ed256055baa8df1432",
|
||||||
|
"Liandate": "2021-08-10",
|
||||||
|
"Anno": "(2021)京0105执34224号",
|
||||||
|
"Executegov": "北京市朝阳区人民法院",
|
||||||
|
"Executestatus": "全部未履行",
|
||||||
|
"Publicdate": "2022-01-07",
|
||||||
|
"Executeno": "(2020)京0105民初51653号",
|
||||||
|
"ActionRemark": "有履行能力而拒不履行生效法律文书确定义务",
|
||||||
|
"Amount": "1098000"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Status": "200",
|
||||||
|
"Message": "【有效请求】查询成功",
|
||||||
|
"OrderNumber": "SHIXINCHECK2022022815011713633136"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 字段说明
|
||||||
|
|
||||||
|
### Executestatus(履行情况)常见值
|
||||||
|
|
||||||
|
| 值 | 描述 |
|
||||||
|
|----|------|
|
||||||
|
| 全部未履行 | 被执行人未履行任何义务 |
|
||||||
|
| 部分履行 | 被执行人已履行部分义务 |
|
||||||
|
| 全部履行 | 被执行人已全部履行义务 |
|
||||||
|
|
||||||
|
### ActionRemark(失信行为)常见类型
|
||||||
|
|
||||||
|
- 有履行能力而拒不履行生效法律文书确定义务
|
||||||
|
- 以伪造证据、暴力、威胁等方法妨碍、抗拒执行
|
||||||
|
- 以虚假诉讼、虚假仲裁或者以隐匿、转移财产等方法规避执行
|
||||||
|
- 违反财产报告制度
|
||||||
|
- 违反限制消费令
|
||||||
|
- 无正当理由拒不履行执行和解协议
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **分页处理**:当 `TotalRecords` 大于 `PageSize` 时,需要分页请求获取完整数据
|
||||||
|
2. **金额格式**:`Amount` 字段为字符串类型,单位为元,使用时需转换为数值类型
|
||||||
|
3. **日期格式**:日期字段格式为 `YYYY-MM-DD`
|
||||||
|
4. **空数据处理**:当 `VerifyResult` 为 `0` 时,`Data` 数组为空
|
||||||
|
5. **案号格式**:`Anno` 和 `Executeno` 包含中文括号,注意字符编码处理
|
||||||
Reference in New Issue
Block a user