qiyu-openapi-sdk
v0.2.0
Published
七鱼客服开放平台 TypeScript/JavaScript SDK
Downloads
148
Maintainers
Readme
七鱼客服开放平台 SDK
七鱼客服开放平台的 TypeScript/JavaScript SDK,提供完整的类型定义和易用的 API 接口。
特性
- ✅ 完整的 TypeScript 类型定义
- ✅ 自动处理鉴权(MD5 + SHA1 + checksum)
- ✅ 基于 Promise 的异步 API
- ✅ 模块化设计,按需使用
- ✅ 完善的错误处理
- ✅ 支持员工管理、工单管理等核心功能
- ✅ 支持客户群详情查询
- ✅ 工单客服信息自动增强(内建缓存,免 N+1 查询)
安装
npm install qiyu-openapi-sdk
# 或
yarn add qiyu-openapi-sdk
# 或
pnpm add qiyu-openapi-sdk快速开始
import { QiyuKfClient } from 'qiyu-openapi-sdk';
// 初始化客户端
const client = new QiyuKfClient({
appKey: 'your_app_key',
appSecret: 'your_app_secret',
});
// 使用员工管理功能
async function example() {
try {
// 获取客服列表
const staffList = await client.staff.list();
console.log('客服列表:', staffList);
// 创建工单
const ticketId = await client.ticket.create({
title: '测试工单',
content: '这是一个测试工单',
staffId: 12345,
userMobile: '18888888888',
});
console.log('工单ID:', ticketId);
} catch (error) {
console.error('发生错误:', error);
}
}
example();初始化
import { QiyuKfClient } from 'qiyu-openapi-sdk';
const client = new QiyuKfClient({
appKey: 'your_app_key', // 必填,七鱼企业 appKey
appSecret: 'your_app_secret', // 必填,七鱼企业 appSecret
baseURL: 'https://qiyukf.com', // 可选,API 基础地址,默认为官方地址
timeout: 30000, // 可选,请求超时(ms),默认 30000
staffCache: { ttl: 300000 }, // 可选,启用客服缓存后工单自动注入名称,默认 TTL=5分钟
});通过 client.staff 访问员工管理,client.ticket 访问工单管理,client.customerGroup 访问客户群详情查询。
客服信息自动增强
启用 staffCache 后,工单查询接口会自动将客服 ID 转换为可读名称,避免 N+1 查询。
const client = new QiyuKfClient({
appKey: 'your_app_key',
appSecret: 'your_app_secret',
staffCache: { ttl: 300000 }, // 传对象即启用
});
const result = await client.ticket.getTicketList({ staffId, filterId });
const t = result.tickets[0];
console.log(t.staffName); // '张工' - 创建人名称
console.log(t.holderName); // '李工' - 受理人名称
console.log(t.followerNames); // { id: name } - 关注人名称映射增强字段
启用后,以下字段自动注入到工单列表、搜索和详情返回中:
| 字段 | 来源 | 说明 |
|------|------|------|
| staffName | staffId | 创建人名称 |
| holderName | holderId | 受理人名称 |
| lastFinishProcessorName | lastFinishProcessor | 最后完结人名称 |
| followerNames | follower | 关注人名称映射(仅列表,详情已有) |
缓存机制
- 首次工单查询时后台异步加载全量客服列表(
staff.list()) - 缓存驻内存(
Map),默认 TTL 5 分钟过期自动刷新 - 并发请求共享同一次加载,不重复请求
- 缓存未就绪时降级返回原始数据,不阻塞业务
- 可通过
client.staffCache手动控制:refresh()、clear()、isEmpty
性能对比:
| 方式 | 查询 100 条工单 |
|------|----------------|
| 无缓存 | 1 次工单 + 100 次 staff.getDetail = 101 次 API 调用 |
| 启用缓存 | 1 次工单 + 1 次 staff.list = 2 次 API 调用 |
枚举
StaffRole - 客服角色
| 值 | 常量 | 说明 |
|----|------|------|
| 0 | NORMAL | 普通客服 |
| 1 | ADMIN | 管理员 |
| 2 | SUPER_ADMIN | 超级管理员 |
| -1 | TICKET | 工单客服 |
| -2 | CALL | 呼叫客服 |
StaffStatus - 客服状态
| 值 | 常量 | 说明 |
|----|------|------|
| 0 | ALL | 所有(查询用) |
| 1 | NORMAL | 正常 |
| 2 | DELETED | 已删除 |
| 3 | DISABLED | 已停用 |
OnlineStatus - 在线客服状态
| 值 | 常量 | 说明 |
|----|------|------|
| 0 | OFFLINE | 离线 |
| 1 | ONLINE | 在线可接待 |
| 2 | SUSPEND | 挂起 |
| 3 | BREAK | 小休 |
| 9 | ADMIN_ONLINE | 管理端在线 |
TicketPriority - 工单优先级
| 值 | 常量 | 说明 |
|----|------|------|
| 2 | LOW | 低 |
| 5 | NORMAL | 一般 |
| 8 | URGENT | 紧急 |
| 10 | CRITICAL | 非常紧急 |
TicketStatus - 工单状态
| 值 | 常量 | 说明 |
|----|------|------|
| 1 | SUBMITTED | 已提交 |
| 5 | PENDING_CLAIM | 待申领 |
| 10 | PROCESSING | 处理中 |
| 20 | FINISHED | 已完结 |
| 25 | REJECTED | 已驳回 |
| 50 | PENDING | 已挂起 |
FieldType - 模板字段类型
| 值 | 常量 | 说明 |
|----|------|------|
| 0 | TEXT | 文本 |
| 1 | SELECT | 单选 |
| 2 | MULTI_SELECT | 多选 |
| 3 | TIME | 时间控件 |
| 6 | CASCADE | 级联 |
| 7 | ATTACHMENT | 附件 |
FieldRequired - 字段必填
| 值 | 常量 | 说明 |
|----|------|------|
| 0 | OPTIONAL | 非必填 |
| 1 | REQUIRED | 必填 |
| 2 | CONDITIONAL | 条件必填 |
EvaluationModel / EvaluationValue - 满意度评价
| 枚举 | 值 | 常量 |
|------|----|------|
| EvaluationModel | 2/3/4/5 | TWO / THREE / FOUR / FIVE |
| EvaluationValue | 0/1/25/50/75/100 | BAD / POOR / FAIR / GOOD / GREAT / EXCELLENT |
EvaluationStatus - 评价状态
| 值 | 常量 | 说明 |
|----|------|------|
| 0 | UNAVAILABLE | 不可评价 |
| 1 | AVAILABLE | 可以评价 |
| 2 | COMPLETED | 已完成 |
| 3 | EXPIRED | 已过期 |
| 4 | INVALID | 已失效 |
FollowerOpType - 关注人操作
| 值 | 常量 | 说明 |
|----|------|------|
| 0 | FOLLOW | 关注 |
| 1 | UNFOLLOW | 取消关注 |
AutoAcceptSwitch - 自动分配开关
| 值 | 常量 |
|----|------|
| 0 | CLOSE |
| 1 | OPEN |
StaffModule - 员工管理
list(params?)
获取客服列表。
const list = await client.staff.list(); // 所有客服
const list = await client.staff.list({ status: StaffStatus.NORMAL }); // 按状态
const list = await client.staff.list({ role: StaffRole.NORMAL }); // 按角色| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| status | number | 否 | StaffStatus: 0=所有, 1=正常, 2=删除, 3=停用 |
| role | number | 否 | StaffRole |
返回 Promise<Staff[]>。
query(params)
条件查询客服信息(多条件全匹配)。
const result = await client.staff.query({ id: 123 });
const result = await client.staff.query({ email: '[email protected]' });| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | number | 条件其一 | 客服ID |
| nickname | string | 条件其一 | 昵称 |
| email | string | 条件其一 | 邮箱 |
| phone | string | 条件其一 | 电话 |
| workNumber | string | 条件其一 | 工号 |
| realname | string | 条件其一 | 姓名 |
| thirdPlatformUserId | string | 条件其一 | 三方客服ID |
| thirdPlatformType | number | 否 | 三方渠道:11=企微, 12=飞书, 13=钉钉, 14=POPO |
返回 Promise<Staff[]>。
getDetail(params)
查询单个客服详情。
const detail = await client.staff.getDetail({ id: 123 });返回 Promise<Staff | null>。
create(params)
创建客服。
import crypto from 'crypto';
const staffId = await client.staff.create({
username: 'staff001',
password: crypto.createHash('md5').update('password123').digest('hex'), // MD5 小写
role: StaffRole.NORMAL,
subRoleId: 12715018,
realname: '张三',
nickname: '小张',
mobile: '13800138000',
email: '[email protected]',
maxSession: 10,
groupIds: [1, 2],
callEnable: 1,
skillScoreChat: 5,
skillScoreIpcc: 3,
});| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| username | string | 是 | 客服账号,字母开头,不超过20位 |
| password | string | 是 | MD5加密后的小写32位字符串 |
| role | number | 是 | StaffRole: 0=普通, 1=管理员, -1=工单客服, -2=呼叫客服 |
| subRoleId | number | 是 | 角色ID(管理后台获取) |
| realname | string | 否 | 不超过20字符 |
| nickname | string | 否 | 不超过20字符 |
| pinyin | string | 否 | 拼音 |
| mobile | string | 否 | 联系电话 |
| email | string | 否 | 邮箱 |
| maxSession | number | 否 | 最大接待数 |
| groupIds | number[] | 否 | 分组ID数组 |
| callEnable | number | 否 | 电话权限 0=否 1=有 |
| skillScoreChat | number | 否 | 在线会话技能值(0-10) |
| skillScoreIpcc | number | 否 | 电话技能值(0-10) |
返回 Promise<number>(客服ID)。
update(params)
修改客服信息。
const ok = await client.staff.update({ id: 100, realname: '李四', maxSession: 15 });参数同 create,id 必填,其余可选。返回 Promise<boolean>。
delete(id)
删除客服(不支持删除超管)。
const ok = await client.staff.delete(100);返回 Promise<boolean>。
updateStatus(id, status)
启用/停用客服。
await client.staff.updateStatus(100, StaffStatus.DISABLED); // 停用
await client.staff.updateStatus(100, StaffStatus.NORMAL); // 启用返回 Promise<boolean>。
login(staffName)
服务端登录(免登录嵌入 iframe)。
const info = await client.staff.login('staff001');
// { sdk_url, corp_code, token }返回 Promise<LoginResult | null>。
listOnline(params?)
查询已登录客服信息(限每分钟500次)。
const online = await client.staff.listOnline(); // 所有
const online = await client.staff.listOnline({ groupIds: [1, 2] }); // 按组返回 Promise<OnlineStaff[]>。
listThirdParty(params)
条件查询外部员工信息(企微/飞书/钉钉/POPO)。
const list = await client.staff.listThirdParty({
platform: 1, // 1=企微, 2=飞书, 3=钉钉, 4=POPO
offset: 0,
limit: 50,
});返回 Promise<ThirdPartyStaff[]>。
updateThirdParty(params)
批量操作外部员工。
const ok = await client.staff.updateThirdParty({
type: 1, // 1=开启IM工单, 2=关闭IM工单, 3=开启IM负责人, 4=关闭
corpMappingId: '7087393',
userMappingIdList: [7239857],
});返回 Promise<boolean>。
createGroup(name)
创建客服组。
const groupId = await client.staff.createGroup('技术支持组');name 不超过60字符。返回 Promise<number>(组ID)。
updateGroup(groupId, name)
修改客服组名称。
const ok = await client.staff.updateGroup(160, '高级技术支持组');返回 Promise<boolean>。
deleteGroup(groupId)
删除客服组。
const ok = await client.staff.deleteGroup(160);返回 Promise<boolean>。
assignToGroup(groupId, kefuIds)
分配客服到客服组。
const ok = await client.staff.assignToGroup(154, [101, 102, 103]);返回 Promise<boolean>。
listGroups(params?)
获取客服组列表。
const groups = await client.staff.listGroups();
const groups = await client.staff.listGroups({ staff: true, emptyGroup: true });返回 Promise<StaffGroup[]>。
listGroupMembers(groupId, role?)
获取客服组下员工列表。
const members = await client.staff.listGroupMembers(123);
const members = await client.staff.listGroupMembers(123, StaffRole.NORMAL);返回 Promise<Staff[]>。
CustomerGroupModule - 客户群管理
getDetail(params)
根据企微客户群的 chatId 查询群资料,包括群名称、群人数、群主、公告、负责人、企微主体、自定义字段、群成员和会话存档同意情况等信息。
const group = await client.customerGroup.getDetail({
chatId: '<wechat-work-group-chat-id>',
});
if (group) {
console.log(group.groupName);
console.log(group.members);
}| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| chatId | string | 是 | 企微客户群的唯一 ID |
请求使用 POST /openapi/crm/group/queryGroupInfo。SDK 自动根据 JSON 请求体生成 appKey、time 和 checksum,返回值已解包为响应中的 data。
返回 Promise<CustomerGroupInfo | null>。HTTP 状态码或业务响应码异常时会抛出错误。
TicketModule - 工单管理
create(params)
创建工单。
const ticketId = await client.ticket.create({
title: '产品使用问题',
content: '无法登录系统',
staffId: 12345, // 操作客服ID
uid: 'user_001', // 用户ID(与 userMobile 二选一)
userName: '王小明',
userMobile: '13800138000',
userEmail: '[email protected]',
priority: TicketPriority.NORMAL,
templateId: 200, // 工单模板ID
typeId: 100, // 工单分类ID
targetStaffId: 12346, // 指定客服(与 targetGroupId 二选一)
followerIds: ['12347'], // 关注人
attachments: [{ // 附件(最多5个, 总大小≤30MB)
fileName: '截图.png',
type: 1, // 1=Base64
payload: 'base64...',
}],
customFields: [{ // 自定义字段
id: fieldId,
fieldId,
value: '选项值',
}],
properties: '{"key":"val"}', // 扩展属性JSON字符串
fromType: 8, // 8=邮件工单
});| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| title | string | 是 | 工单标题 |
| content | string | 是 | 工单内容 |
| staffId | number | 是 | 操作客服ID |
| uid | string | 条件 | 用户ID(与 userMobile 二选一) |
| userMobile | string | 条件 | 用户手机号 |
| userName | string | 否 | 用户名称 |
| userEmail | string | 否 | 用户邮箱 |
| priority | number | 否 | TicketPriority: 2=低, 5=一般, 8=紧急, 10=非常紧急 |
| templateId | number | 否 | 模板ID |
| typeId | number | 否 | 分类ID |
| targetStaffId | number | 否 | 处理客服(与 targetGroupId 二选一) |
| targetGroupId | number | 否 | 处理客服组 |
| followerIds | string[] | 否 | 关注人 |
| attachments | Attachment[] | 否 | 附件 |
| customFields | CustomField[] | 否 | 自定义字段 |
| properties | string | 否 | 扩展属性,JSON 字符串格式 |
| fromType | number | 否 | 来源,8=邮件工单 |
返回 Promise<number>(工单ID)。
modify(params)
通用修改工单接口(单字段修改)。
// type: 0=自定义字段, 1=标题, 2=优先级, 3=分类
const ok = await client.ticket.modify({
ticketId: 12345, staffId: 100, type: 1, value: '新标题'
});返回 Promise<boolean>。
modifyTitle / modifyPriority / modifyCategory / modifyCustomField
modify 的便捷方法。
await client.ticket.modifyTitle(ticketId, staffId, '新标题');
await client.ticket.modifyPriority(ticketId, staffId, TicketPriority.URGENT);
await client.ticket.modifyCategory(ticketId, staffId, categoryId);
await client.ticket.modifyCustomField(ticketId, staffId, customId, fieldId, '值');返回 Promise<boolean>。
batchModify(params)
批量修改工单自定义字段(最多20个)。
const ok = await client.ticket.batchModify({
ticketId: 12345,
staffId: 100,
customFields: [
{ id: customId1, fieldId: fieldId1, value: '值1' },
{ id: customId2, fieldId: fieldId2, value: '值2' },
],
});返回 Promise<boolean>。
apply(params)
申领工单。
const ok = await client.ticket.apply({ ticketId: 12345, staffId: 100 });返回 Promise<boolean>。
transfer(params)
转交工单。
const ok = await client.ticket.transfer({
ticketId: 12345,
staffId: 100,
targetStaffId: 101, // 与 targetGroupId 二选一
comment: '转交说明',
attachments: [],
});返回 Promise<boolean>。
reply(params)
回复工单。
const ok = await client.ticket.reply({
ticketId: 12345,
staffId: 100,
comment: '已处理',
customerSee: true, // 访客是否可见
attachments: [],
});返回 Promise<boolean>。
reminder(params)
催单。
const reminderId = await client.ticket.reminder({
ticketId: 12345, staffId: 100, comment: '请尽快处理',
});返回 Promise<number>(催单ID)。
finish(params)
完结工单。
const ok = await client.ticket.finish({
ticketId: 12345,
staffId: 100,
comment: '问题已解决',
nodeId: 5001, // 画布模式必传
});返回 Promise<boolean>。
reopen(params)
重新打开工单。
const ok = await client.ticket.reopen({
ticketId: 12345,
staffId: 100,
targetStaff: 101, // 与 targetGroup 二选一
comment: '需继续处理',
});返回 Promise<boolean>。
getTemplateList(params?)
获取工单模板列表。
const templates = await client.ticket.getTemplateList(); // 启用
const templates = await client.ticket.getTemplateList({ status: -1 }); // 全部| status | 说明 | |--------|------| | -1 | 全部状态 | | 0 | 停用 | | 1 | 启用 | | 2 | 已删除 |
返回 Promise<TicketTemplate[]>。
getTemplateFields(params)
获取模板字段列表。对于级联字段(type=6),cascadeData 会自动填充完整的级联树,无需额外调用 getCascadeField。
const fields = await client.ticket.getTemplateFields({ templateId: 200 });
for (const f of fields) {
if (f.type === FieldType.CASCADE && f.cascadeData) {
// f.cascadeData 已包含完整级联树
console.log(f.name, f.cascadeData[0].children);
} else if (f.type === FieldType.SELECT) {
// f.description 是选项数组 [{text: "选项1"}, ...]
console.log(f.name, f.description);
}
}返回 Promise<TemplateField[]>。
getFilterList(params?)
获取工单过滤器列表。
const filters = await client.ticket.getFilterList(); // 管理员视角
const filters = await client.ticket.getFilterList({ staffId: 123, status: 1 });返回 Promise<TicketFilter[]>。
getFilterCount(params)
获取过滤器下的工单数量。
const count = await client.ticket.getFilterCount({ staffId: 123, filterId: 456 });返回 Promise<number>。
getTicketList(params)
获取过滤器下的工单列表。
const result = await client.ticket.getTicketList({
staffId: 123, filterId: 456,
limit: 20, offset: 0,
sortBy: 'ct', // ct=创建时间, pr=优先级, ut=最后操作时间
order: 'desc',
});
// { total: 100, tickets: [...] }limit 最大50。返回 Promise<TicketListResult>。
searchTickets(params)
搜索工单(ticketId 与 mobile 二选一)。
const result = await client.ticket.searchTickets({
ticketId: 12345,
limit: 50, withCustomField: true, withMailReplyList: true,
});
// 或按手机号+时间范围
const result = await client.ticket.searchTickets({
mobile: '13800138000',
start: Date.now() - 30 * 86400000,
end: Date.now(),
});limit 最大500。返回 Promise<TicketListResult>。
getTicketDetail(params)
获取工单详情。
const detail = await client.ticket.getTicketDetail({ ticketId: 12345 });
// detail.custom — 自定义字段列表
// detail.status — TicketStatus返回 Promise<TicketDetailResponse | null>。
getTicketLogs(params)
获取工单日志。
const logs = await client.ticket.getTicketLogs({ ticketId: 12345 });返回 Promise<TicketLogItem[]>。
updateFollowers(params)
更新工单关注人。
const result = await client.ticket.updateFollowers({
ticketId: 12345, staffId: 100,
optype: FollowerOpType.FOLLOW,
followers: ['101', '102'],
});返回 Promise<UpdateFollowersResult>。
getCascadeField(params)
获取级联字段的完整树(一般通过 getTemplateFields 的 cascadeData 自动获取)。
const tree = await client.ticket.getCascadeField({ fieldId: 4696492 });返回 Promise<CascadeFieldResult>。
getCategoryList(params)
获取工单分类列表。
const categories = await client.ticket.getCategoryList({ type: 2 });返回 Promise<TicketCategory[]>。
getTicketCategories()
获取所有工单分类的快捷方法(等价于 getCategoryList({ type: 2 }))。
const categories = await client.ticket.getTicketCategories();返回 Promise<TicketCategory[]>。
batchAutoAcceptConfig(params)
批量设置员工工单自动分配开关。
const result = await client.ticket.batchAutoAcceptConfig({
itemList: [
{ staffId: 123, open: AutoAcceptSwitch.OPEN },
{ staffId: 456, open: AutoAcceptSwitch.CLOSE },
],
});返回 Promise<BatchAutoAcceptConfigResult>。
ticketCallback(params)
工单流程节点回调。
const ok = await client.ticket.ticketCallback({
ticketId: 123, nodeExecRecordId: 456, conditionLineId: 789,
result: true,
});返回 Promise<boolean>。
pushImCard(params)
推送三方渠道卡片消息。
const ok = await client.ticket.pushImCard({
title: '提醒', content: '有工单', ticketId: 123,
corpMappingId: 100, userMappingId: 200,
});返回 Promise<boolean>。
getEvaluationStatus(ticketId)
获取工单满意度评价状态。
const status = await client.ticket.getEvaluationStatus(12345);
// 0=不可评价, 1=可评价, 2=已完成, 3=已过期, 4=已失效返回 Promise<number>,参见 EvaluationStatus 枚举。
getEvaluationRule(ticketId)
获取工单满意度评价规则。
const rule = await client.ticket.getEvaluationRule(12345);返回 Promise<object | null>。
submitEvaluation(params)
提交满意度评价。
const ok = await client.ticket.submitEvaluation({
ticketId: 12345,
evaluationValue: EvaluationValue.EXCELLENT,
evaluationModel: EvaluationModel.FIVE,
tagList: ['服务好'],
content: '非常满意',
evaluationWay: 4, // 固定为 4
isSolved: true,
});返回 Promise<boolean>。
getEvaluationResult(ticketId)
获取满意度评价结果。
const result = await client.ticket.getEvaluationResult(12345);返回 Promise<object | null>。
核心类型
CustomerGroupInfo
interface CustomerGroupInfo {
archiveInfo: CustomerGroupArchiveInfo;
chatId: string;
createtime: number; // 13 位毫秒时间戳
customFieldValues: CustomerGroupCustomFieldValue[];
customerMemberSize: number;
groupIcon: string;
groupName: string;
groupNotice: string;
lastSessionTime: number; // 13 位毫秒时间戳
leader: string;
leaderId: number;
memberSize: number;
members: CustomerGroupMember[];
ownerName: string;
platformType: number;
staffMemberSize: number;
vipLevel: number;
wxCorpId: string;
wxCorpName: string;
}CustomerGroupMember
interface CustomerGroupMember {
userId: number;
userGroupName: string;
userGroupIcon: string;
userGroupRole: number;
escrow: number;
friendFlag?: boolean;
}CustomerGroupCustomFieldValue、CustomerGroupArchiveInfo
客户群详情中的自定义字段值和会话存档信息分别对应这两个类型。真实接口的自定义字段 value 返回字符串,选择项等结构化定义则以 JSON 字符串存放在 description 中。
Staff
interface Staff {
id: number;
username: string;
realname?: string;
nickname?: string;
role: number; // StaffRole
roleId?: number;
phone?: string;
email?: string;
status: number; // StaffStatus: 1=正常, 2=删除, 3=停用
createtime?: number;
updateTime?: number;
maxServiceCount?: number;
skillScoreChat?: number;
skillScoreIpcc?: number;
workNumber?: string;
staffGroupIds?: number[];
groups?: StaffGroup[];
}CustomField
interface CustomField {
id: number; // 字段实例ID(工单详情中的 custom.id)
fieldId?: number; // 字段定义ID(模板中的 fieldId)
value: string; // 字段值
name?: string; // 字段名
}Attachment
interface Attachment {
fileName: string; // 文件名
type: number; // 1=Base64编码
payload: string; // Base64 编码的文件内容
}TicketDetailResponse
interface TicketDetailResponse {
id: number;
title: string;
content: string;
status: number; // TicketStatus
staffId: number; // 创建人 ID
staffName?: string; // 创建人名称(缓存增强)
holderId?: number; // 受理人 ID
holderName?: string; // 受理人名称(缓存增强)
templateId: number;
templateName?: string;
priority?: number; // TicketPriority
custom?: CustomField[]; // 自定义字段列表
follower?: Record<number, string>; // 关注人(id → 名称)
createTime: number;
attachments?: Array<{ name: string; size: number; type: number; url: string }>;
questionType?: { id: number; name: string; parent: number; path: string };
// ... 更多字段
}TicketDetail(列表/搜索)
interface TicketDetail {
id: number;
title: string;
content: string;
status: number; // TicketStatus
staffId: number; // 创建人 ID
staffName?: string; // 创建人名称(缓存增强)
holderId?: number; // 受理人 ID
holderName?: string; // 受理人名称(缓存增强)
lastFinishProcessor?: number; // 最后完结人 ID
lastFinishProcessorName?: string; // 最后完结人名称(缓存增强)
follower?: string[]; // 关注人 ID 列表
followerNames?: Record<number, string>; // 关注人名称映射(缓存增强)
priority?: number; // TicketPriority
templateId: number;
createTime: number;
groupId?: number;
// ... 更多字段
}TemplateField
interface TemplateField {
id: number;
fieldId: number;
name: string;
required: number; // FieldRequired: 0=非必填, 1=必填, 2=条件必填
type: number; // FieldType: 0=文本, 1=单选, 2=多选, 3=时间, 6=级联, 7=附件
status: number;
description?: any; // 单选/多选的选项列表 [{text: "选项1"}, ...]
prefill?: any; // 预填值
hint?: string;
customer: number; // 0=关, 1=开(访客填写)
cascadeData?: CascadeFieldNode[]; // 级联字段完整树(自动填充)
}CascadeFieldNode
interface CascadeFieldNode {
id: number;
name: string;
children?: CascadeFieldNode[];
disabled: boolean;
highlight?: string;
ai?: number;
sort?: number;
cloud?: boolean;
}TicketListResult
interface TicketListResult {
total: number;
tickets: TicketDetail[];
}错误码 (ErrorCode)
| 常量 | 值 | 说明 |
|------|----|------|
| OK | 200 | 成功 |
| INVALID_APP_KEY | 14001 | 无效 appKey |
| INVALID_CHECKSUM | 14002 | checksum 错误 |
| INVALID_TIME | 14003 | 时间戳错误 |
| INVALID_CONTENT | 14004 | 请求内容格式错误 |
| INVALID_IP | 14008 | IP 不在白名单 |
| RATE_LIMIT | 14009 | 请求频率超限 |
| STAFF_NOT_EXIST | 14100 | 客服不存在 |
| CATEGORY_NOT_EXIST | 14101 | 分类不存在 |
| GROUP_NOT_EXIST | 14102 | 客服组不存在 |
| USER_NOT_EXIST | 14105 | 用户不存在 |
| TICKET_NOT_EXIST | 14106 | 工单不存在 |
| TICKET_DUPLICATE | 14108 | 工单重复创建 |
| TEMPLATE_NOT_EXIST | 14110 | 模板不存在 |
| STAFF_ALREADY_EXISTS | 14704 | 客服账号已存在 |
| STAFF_USERNAME_INVALID | 14705 | 客服账号不合法 |
| SERVER_ERROR | 14500 | 服务器错误 |
| DATA_TOO_LARGE | 14501 | 数据量过大 |
| PERMISSION_DENIED | 14515 | 权限不足 |
| SERVICE_UNAVAILABLE | 16001 | 服务不可用 |
| TICKET_NO_PERMISSION | 8802 | 客服无权限 |
错误处理
SDK 自动检查 API 响应,code !== 200 时抛出 Error:
try {
await client.ticket.create({ ... });
} catch (error) {
console.error(error.message);
// "API Error 14110: 模板不存在"
}timeout 超时未响应则抛出 AbortError。HTTP 状态码非 2xx 则抛出含响应体的 Error。
TypeScript 支持
SDK 提供了完整的 TypeScript 类型定义:
import {
QiyuKfClient,
StaffCache,
Staff,
StaffGroup,
CreateStaffParams,
TicketDetail,
TicketDetailResponse,
CreateTicketParams,
CustomerGroupInfo,
CustomerGroupMember,
CustomerGroupDetailParams,
ErrorCode,
} from 'qiyu-openapi-sdk';
// 所有参数都有类型检查
const params: CreateTicketParams = {
title: '测试工单',
content: '内容',
staffId: 123,
userMobile: '13800138000',
};
const ticketId: number = await client.ticket.create(params);开发与构建
# 安装依赖
npm install
# 开发模式(监听文件变化)
npm run dev
# 构建
npm run build
# 代码检查
npm run lint
# 代码格式化
npm run format许可证
MIT License
