App.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /*global Dingdocs*/
  2. import { useEffect, useState, useCallback } from 'react';
  3. import { initView } from 'dingtalk-docs-cool-app';
  4. import { Typography, Button, Card, Select, Input, Spin } from 'dingtalk-design-desktop';
  5. import './style.css';
  6. interface ApiParam {
  7. paramName: string;
  8. paramDesc: string;
  9. exampleValue?: string;
  10. valueList?: string[];
  11. value?: string;
  12. }
  13. interface ApiItem {
  14. id: string;
  15. apiName: string;
  16. url: string;
  17. description: string;
  18. aiTableId: string;
  19. aiTablename: string;
  20. apiParamAuth: ApiParam[];
  21. }
  22. interface ApiResponse {
  23. code: number;
  24. msg: string;
  25. data?: any;
  26. }
  27. interface UserInfo {
  28. userId: string;
  29. name?: string;
  30. avatar?: string;
  31. mobile?: string;
  32. jobnumber?: string;
  33. department?: number[];
  34. permissions?: string[];
  35. [key: string]: any;
  36. }
  37. function App() {
  38. const [loading, setLoading] = useState<boolean>(true);
  39. const [apiList, setApiList] = useState<ApiItem[]>([]);
  40. const [currentApi, setCurrentApi] = useState<ApiItem | null>(null);
  41. const [apiParams, setApiParams] = useState<Record<string, string>>({});
  42. const [result, setResult] = useState<string>('');
  43. const [callLoading, setCallLoading] = useState<boolean>(false);
  44. const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
  45. const [error, setError] = useState<string>('');
  46. const [customTableId, setCustomTableId] = useState<string>('');
  47. const [customTableName, setCustomTableName] = useState<string>('');
  48. const loadApiList = useCallback(async () => {
  49. try {
  50. const list = await Dingdocs.script.run('getApiList');
  51. setApiList(list);
  52. } catch (error: any) {
  53. console.error('加载 API 列表失败:', error);
  54. setError(error.message || '加载 API 列表失败');
  55. }
  56. }, []);
  57. const handleConfigPermission = useCallback(async (token: string) => {
  58. try {
  59. const currentUrl = window.location.href.split('#')[0];
  60. const response = await fetch(`https://openapi.julefood.cn:8082/api/sys_user/jsapi/config?url=${encodeURIComponent(currentUrl)}`, {
  61. headers: {
  62. 'Authorization': `Bearer ${token}`,
  63. 'token': token
  64. }
  65. });
  66. const config = await response.json();
  67. if (config.code !== 200) {
  68. return;
  69. }
  70. const result = config.result || {};
  71. if (!result.agentId || !result.corpId || !result.signature) {
  72. return;
  73. }
  74. await Dingdocs.base.host.configPermission(
  75. result.agentId,
  76. result.corpId,
  77. result.timeStamp,
  78. result.nonceStr,
  79. result.signature,
  80. ['DingdocsScript.base.readWriteAll']
  81. );
  82. } catch (error: any) {
  83. console.error('权限配置失败:', error);
  84. }
  85. }, []);
  86. const handleAutoLogin = useCallback(async () => {
  87. try {
  88. const urlParams = new URLSearchParams(window.location.search);
  89. const corpId = urlParams.get('corpId') || '';
  90. const authResult = await Dingdocs.base.host.getAuthCode(corpId);
  91. const authCode = authResult.code;
  92. const user = await Dingdocs.script.run('login', authCode);
  93. setUserInfo(user);
  94. const token = await Dingdocs.script.run('getToken');
  95. await handleConfigPermission(token);
  96. } catch (error: any) {
  97. console.error('自动登录失败:', error);
  98. setError(error.message || '登录失败');
  99. throw error;
  100. }
  101. }, [handleConfigPermission]);
  102. const loadDocumentInfo = useCallback(async () => {
  103. try {
  104. const info = await Dingdocs.script.run('getDocumentInfo');
  105. setCustomTableId(info?.uuid || '');
  106. setCustomTableName(info?.currentSheet || '');
  107. } catch (error: any) {
  108. console.error('获取文档信息失败:', error);
  109. }
  110. }, []);
  111. const handleApiChange = (apiId: string) => {
  112. const api = apiList.find(item => item.id === apiId);
  113. if (api) {
  114. setCurrentApi(api);
  115. const params: Record<string, string> = {};
  116. api.apiParamAuth.forEach(param => {
  117. params[param.paramName] = param.value || '';
  118. });
  119. setApiParams(params);
  120. setResult('');
  121. } else {
  122. setCurrentApi(null);
  123. setApiParams({});
  124. setResult('');
  125. }
  126. };
  127. const handleParamChange = (paramName: string, value: string) => {
  128. setApiParams(prev => ({
  129. ...prev,
  130. [paramName]: value
  131. }));
  132. };
  133. const handleCallApi = async () => {
  134. if (!currentApi || callLoading) return;
  135. setCallLoading(true);
  136. setResult('');
  137. const apiItem = {
  138. ...currentApi,
  139. aiTableId: customTableId || currentApi.aiTableId,
  140. aiTablename: customTableName || currentApi.aiTablename,
  141. apiParamAuth: currentApi.apiParamAuth.map(p => ({
  142. paramName: p.paramName,
  143. value: apiParams[p.paramName] || p.value || ''
  144. }))
  145. };
  146. try {
  147. const response: ApiResponse = await Dingdocs.script.run('callApi', apiItem);
  148. setResult(JSON.stringify(response, null, 2));
  149. } catch (error: any) {
  150. setResult(`调用失败: ${error.message}`);
  151. } finally {
  152. setCallLoading(false);
  153. }
  154. };
  155. useEffect(() => {
  156. initView({
  157. onReady: async () => {
  158. setLoading(true);
  159. setError('');
  160. try {
  161. await handleAutoLogin();
  162. await loadApiList();
  163. loadDocumentInfo();
  164. } catch (error: any) {
  165. console.error('初始化失败:', error);
  166. setError(error.message || '初始化失败');
  167. } finally {
  168. setLoading(false);
  169. }
  170. },
  171. });
  172. }, [handleAutoLogin, loadApiList, loadDocumentInfo]);
  173. if (loading) {
  174. return (
  175. <div className="page">
  176. <div className="loading">
  177. <Spin size="small" />
  178. <Typography.Text className="loading-text">加载中...</Typography.Text>
  179. </div>
  180. </div>
  181. );
  182. }
  183. if (error) {
  184. return (
  185. <div className="page">
  186. <div className="login-container">
  187. <Card size="small" className="login-card">
  188. <Typography.Text type="danger">{error}</Typography.Text>
  189. </Card>
  190. </div>
  191. </div>
  192. );
  193. }
  194. return (
  195. <div className="page">
  196. <div className="header">
  197. <div className="header-content">
  198. <Typography.Title level={4}>API 调用测试</Typography.Title>
  199. <div className="user-info">
  200. <Typography.Text className="user-name">{userInfo?.name || userInfo?.userId || '用户'}</Typography.Text>
  201. </div>
  202. </div>
  203. </div>
  204. <div className="content">
  205. <Card size="small" className="main-card">
  206. <div className="form-item">
  207. <Typography.Text className="label">选择 API</Typography.Text>
  208. <Select
  209. className="api-select"
  210. placeholder="请选择要调用的 API 接口"
  211. onChange={handleApiChange}
  212. value={currentApi?.id || ''}
  213. >
  214. {apiList.map(api => (
  215. <Select.Option key={api.id} value={api.id}>
  216. {api.apiName}
  217. </Select.Option>
  218. ))}
  219. </Select>
  220. </div>
  221. {currentApi && (
  222. <div className="api-detail">
  223. <Card size="small" className="info-card">
  224. <div className="info-row">
  225. <Typography.Text className="info-label">API 名称:</Typography.Text>
  226. <Typography.Text strong>{currentApi.apiName}</Typography.Text>
  227. </div>
  228. <div className="info-row">
  229. <Typography.Text className="info-label">功能描述:</Typography.Text>
  230. <Typography.Text>{currentApi.description}</Typography.Text>
  231. </div>
  232. </Card>
  233. <Typography.Title level={5} className="params-title">文档信息</Typography.Title>
  234. <div className="params-container">
  235. <div className="param-item">
  236. <Typography.Text className="param-label">
  237. 文档 ID
  238. <span className="param-name">(documentId)</span>
  239. </Typography.Text>
  240. <Input
  241. className="param-input"
  242. placeholder="请输入文档 ID"
  243. value={customTableId || ''}
  244. onChange={(e) => setCustomTableId(e.target.value)}
  245. />
  246. </div>
  247. <div className="param-item">
  248. <Typography.Text className="param-label">
  249. 表格名称
  250. <span className="param-name">(tableName)</span>
  251. </Typography.Text>
  252. <Input
  253. className="param-input"
  254. placeholder="请输入表格名称"
  255. value={customTableName || ''}
  256. onChange={(e) => setCustomTableName(e.target.value)}
  257. />
  258. </div>
  259. </div>
  260. <Typography.Title level={5} className="params-title">请求参数</Typography.Title>
  261. <div className="params-container">
  262. {currentApi.apiParamAuth.map(param => (
  263. <div key={param.paramName} className="param-item">
  264. <Typography.Text className="param-label">
  265. {param.paramDesc}
  266. <span className="param-name">({param.paramName})</span>
  267. </Typography.Text>
  268. {param.valueList && param.valueList.length > 0 && param.valueList.some(v => v) ? (
  269. <Select
  270. className="param-select"
  271. placeholder="请选择"
  272. value={apiParams[param.paramName] || ''}
  273. onChange={(value) => handleParamChange(param.paramName, value)}
  274. >
  275. {param.valueList.map(val => (
  276. <Select.Option key={val} value={val}>
  277. {val}
  278. </Select.Option>
  279. ))}
  280. </Select>
  281. ) : (
  282. <Input
  283. className="param-input"
  284. placeholder={param.exampleValue || '请输入'}
  285. value={apiParams[param.paramName] || ''}
  286. onChange={(e) => handleParamChange(param.paramName, e.target.value)}
  287. />
  288. )}
  289. </div>
  290. ))}
  291. </div>
  292. <div className="action-bar">
  293. <Button
  294. type="primary"
  295. loading={callLoading}
  296. onClick={handleCallApi}
  297. className="call-btn"
  298. >
  299. 执行调用
  300. </Button>
  301. </div>
  302. {result && (
  303. <Card size="small" className="result-card">
  304. <Typography.Title level={5} className="result-title">返回结果</Typography.Title>
  305. <pre className="result-content">{result}</pre>
  306. </Card>
  307. )}
  308. </div>
  309. )}
  310. </Card>
  311. </div>
  312. </div>
  313. );
  314. }
  315. export default App;