App.tsx 11 KB

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