api-patterns.mdc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. ---
  2. alwaysApply: false
  3. ---
  4. ### API 设计模式
  5. #### 统一 API 封装
  6. **基础请求封装**
  7. ```javascript
  8. // utils/request.js
  9. const BASE_URL = 'https://api.example.com'
  10. const request = {
  11. // GET 请求
  12. get(url, params = {}) {
  13. return new Promise((resolve, reject) => {
  14. uni.request({
  15. url: `${BASE_URL}${url}`,
  16. method: 'GET',
  17. data: params,
  18. header: {
  19. 'Authorization': uni.getStorageSync('token'),
  20. 'Content-Type': 'application/json'
  21. },
  22. success: (res) => {
  23. if (res.statusCode === 200) {
  24. resolve(res.data)
  25. } else {
  26. this.handleError(res)
  27. reject(res)
  28. }
  29. },
  30. fail: (err) => {
  31. this.handleError(err)
  32. reject(err)
  33. }
  34. })
  35. })
  36. },
  37. // POST 请求
  38. post(url, data = {}) {
  39. return new Promise((resolve, reject) => {
  40. uni.request({
  41. url: `${BASE_URL}${url}`,
  42. method: 'POST',
  43. data: data,
  44. header: {
  45. 'Authorization': uni.getStorageSync('token'),
  46. 'Content-Type': 'application/json'
  47. },
  48. success: (res) => {
  49. if (res.statusCode === 200) {
  50. resolve(res.data)
  51. } else {
  52. this.handleError(res)
  53. reject(res)
  54. }
  55. },
  56. fail: (err) => {
  57. this.handleError(err)
  58. reject(err)
  59. }
  60. })
  61. })
  62. },
  63. // 统一错误处理
  64. handleError(error) {
  65. let message = '请求失败'
  66. if (error.statusCode) {
  67. switch (error.statusCode) {
  68. case 401:
  69. message = '登录已过期,请重新登录'
  70. // 跳转到登录页
  71. uni.navigateTo({ url: '/pages/login/login' })
  72. break
  73. case 403:
  74. message = '没有权限访问'
  75. break
  76. case 404:
  77. message = '请求的资源不存在'
  78. break
  79. case 500:
  80. message = '服务器内部错误'
  81. break
  82. default:
  83. message = error.data?.message || '请求失败'
  84. }
  85. }
  86. uni.showToast({
  87. title: message,
  88. icon: 'none',
  89. duration: 2000
  90. })
  91. }
  92. }
  93. export default request
  94. ```
  95. #### API 模块化组织
  96. **按业务模块组织API**
  97. ```javascript
  98. // api/user.js
  99. import request from '@/utils/request'
  100. export const userApi = {
  101. // 登录
  102. login(data) {
  103. return request.post('/user/login', data)
  104. },
  105. // 获取用户信息
  106. getUserInfo() {
  107. return request.get('/user/info')
  108. },
  109. // 更新用户信息
  110. updateUser(data) {
  111. return request.post('/user/update', data)
  112. }
  113. }
  114. // api/order.js
  115. export const orderApi = {
  116. // 获取订单列表
  117. getOrderList(params) {
  118. return request.get('/order/list', params)
  119. },
  120. // 创建订单
  121. createOrder(data) {
  122. return request.post('/order/create', data)
  123. }
  124. }
  125. ```
  126. #### 数据状态管理模式
  127. **加载状态处理**
  128. ```javascript
  129. // 在页面或组件中
  130. export default {
  131. data() {
  132. return {
  133. loading: false,
  134. list: [],
  135. error: null,
  136. empty: false
  137. }
  138. },
  139. methods: {
  140. async loadData() {
  141. this.loading = true
  142. this.error = null
  143. try {
  144. const result = await orderApi.getOrderList()
  145. this.list = result.data || []
  146. this.empty = this.list.length === 0
  147. } catch (error) {
  148. this.error = error.message
  149. } finally {
  150. this.loading = false
  151. }
  152. }
  153. }
  154. }
  155. ```
  156. **分页加载模式**
  157. ```javascript
  158. export default {
  159. data() {
  160. return {
  161. list: [],
  162. page: 1,
  163. pageSize: 20,
  164. total: 0,
  165. loading: false,
  166. loadingMore: false,
  167. noMore: false
  168. }
  169. },
  170. methods: {
  171. // 初始加载
  172. async loadData() {
  173. this.page = 1
  174. this.loading = true
  175. try {
  176. const result = await orderApi.getOrderList({
  177. page: this.page,
  178. pageSize: this.pageSize
  179. })
  180. this.list = result.data || []
  181. this.total = result.total || 0
  182. this.noMore = this.list.length >= this.total
  183. } catch (error) {
  184. console.error('加载失败:', error)
  185. } finally {
  186. this.loading = false
  187. }
  188. },
  189. // 加载更多
  190. async loadMore() {
  191. if (this.loadingMore || this.noMore) return
  192. this.page += 1
  193. this.loadingMore = true
  194. try {
  195. const result = await orderApi.getOrderList({
  196. page: this.page,
  197. pageSize: this.pageSize
  198. })
  199. const newData = result.data || []
  200. this.list = [...this.list, ...newData]
  201. this.noMore = this.list.length >= result.total
  202. } catch (error) {
  203. this.page -= 1 // 回滚页码
  204. console.error('加载更多失败:', error)
  205. } finally {
  206. this.loadingMore = false
  207. }
  208. },
  209. // 下拉刷新
  210. async onRefresh() {
  211. await this.loadData()
  212. uni.stopPullDownRefresh()
  213. }
  214. }
  215. }
  216. ```
  217. #### 缓存策略
  218. **数据缓存模式**
  219. ```javascript
  220. // utils/cache.js
  221. export const cache = {
  222. // 设置缓存
  223. set(key, data, expire = 3600000) { // 默认1小时
  224. const item = {
  225. data,
  226. expire: Date.now() + expire
  227. }
  228. uni.setStorageSync(key, JSON.stringify(item))
  229. },
  230. // 获取缓存
  231. get(key) {
  232. try {
  233. const item = JSON.parse(uni.getStorageSync(key) || '{}')
  234. if (item.expire && Date.now() > item.expire) {
  235. uni.removeStorageSync(key)
  236. return null
  237. }
  238. return item.data
  239. } catch {
  240. return null
  241. }
  242. },
  243. // 删除缓存
  244. remove(key) {
  245. uni.removeStorageSync(key)
  246. }
  247. }
  248. // 在 API 中使用缓存
  249. export const userApi = {
  250. async getUserInfo(useCache = true) {
  251. const cacheKey = 'user_info'
  252. if (useCache) {
  253. const cached = cache.get(cacheKey)
  254. if (cached) return cached
  255. }
  256. const result = await request.get('/user/info')
  257. cache.set(cacheKey, result)
  258. return result
  259. }
  260. }
  261. ```
  262. #### 表单处理模式
  263. **表单验证和提交**
  264. ```javascript
  265. export default {
  266. data() {
  267. return {
  268. form: {
  269. name: '',
  270. phone: '',
  271. email: ''
  272. },
  273. rules: {
  274. name: { required: true, message: '请输入姓名' },
  275. phone: { required: true, pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' },
  276. email: { pattern: /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/, message: '请输入正确的邮箱' }
  277. },
  278. submitting: false
  279. }
  280. },
  281. methods: {
  282. // 表单验证
  283. validateForm() {
  284. for (const field in this.rules) {
  285. const rule = this.rules[field]
  286. const value = this.form[field]
  287. if (rule.required && !value) {
  288. uni.showToast({ title: rule.message, icon: 'none' })
  289. return false
  290. }
  291. if (rule.pattern && value && !rule.pattern.test(value)) {
  292. uni.showToast({ title: rule.message, icon: 'none' })
  293. return false
  294. }
  295. }
  296. return true
  297. },
  298. // 提交表单
  299. async submitForm() {
  300. if (!this.validateForm()) return
  301. this.submitting = true
  302. try {
  303. await userApi.updateUser(this.form)
  304. uni.showToast({ title: '保存成功' })
  305. uni.navigateBack()
  306. } catch (error) {
  307. console.error('提交失败:', error)
  308. } finally {
  309. this.submitting = false
  310. }
  311. }
  312. }
  313. }
  314. ```
  315. ### API 设计模式
  316. #### 统一 API 封装
  317. **基础请求封装**
  318. ```javascript
  319. // utils/request.js
  320. const BASE_URL = 'https://api.example.com'
  321. const request = {
  322. // GET 请求
  323. get(url, params = {}) {
  324. return new Promise((resolve, reject) => {
  325. uni.request({
  326. url: `${BASE_URL}${url}`,
  327. method: 'GET',
  328. data: params,
  329. header: {
  330. 'Authorization': uni.getStorageSync('token'),
  331. 'Content-Type': 'application/json'
  332. },
  333. success: (res) => {
  334. if (res.statusCode === 200) {
  335. resolve(res.data)
  336. } else {
  337. this.handleError(res)
  338. reject(res)
  339. }
  340. },
  341. fail: (err) => {
  342. this.handleError(err)
  343. reject(err)
  344. }
  345. })
  346. })
  347. },
  348. // POST 请求
  349. post(url, data = {}) {
  350. return new Promise((resolve, reject) => {
  351. uni.request({
  352. url: `${BASE_URL}${url}`,
  353. method: 'POST',
  354. data: data,
  355. header: {
  356. 'Authorization': uni.getStorageSync('token'),
  357. 'Content-Type': 'application/json'
  358. },
  359. success: (res) => {
  360. if (res.statusCode === 200) {
  361. resolve(res.data)
  362. } else {
  363. this.handleError(res)
  364. reject(res)
  365. }
  366. },
  367. fail: (err) => {
  368. this.handleError(err)
  369. reject(err)
  370. }
  371. })
  372. })
  373. },
  374. // 统一错误处理
  375. handleError(error) {
  376. let message = '请求失败'
  377. if (error.statusCode) {
  378. switch (error.statusCode) {
  379. case 401:
  380. message = '登录已过期,请重新登录'
  381. // 跳转到登录页
  382. uni.navigateTo({ url: '/pages/login/login' })
  383. break
  384. case 403:
  385. message = '没有权限访问'
  386. break
  387. case 404:
  388. message = '请求的资源不存在'
  389. break
  390. case 500:
  391. message = '服务器内部错误'
  392. break
  393. default:
  394. message = error.data?.message || '请求失败'
  395. }
  396. }
  397. uni.showToast({
  398. title: message,
  399. icon: 'none',
  400. duration: 2000
  401. })
  402. }
  403. }
  404. export default request
  405. ```
  406. #### API 模块化组织
  407. **按业务模块组织API**
  408. ```javascript
  409. // api/user.js
  410. import request from '@/utils/request'
  411. export const userApi = {
  412. // 登录
  413. login(data) {
  414. return request.post('/user/login', data)
  415. },
  416. // 获取用户信息
  417. getUserInfo() {
  418. return request.get('/user/info')
  419. },
  420. // 更新用户信息
  421. updateUser(data) {
  422. return request.post('/user/update', data)
  423. }
  424. }
  425. // api/order.js
  426. export const orderApi = {
  427. // 获取订单列表
  428. getOrderList(params) {
  429. return request.get('/order/list', params)
  430. },
  431. // 创建订单
  432. createOrder(data) {
  433. return request.post('/order/create', data)
  434. }
  435. }
  436. ```
  437. #### 数据状态管理模式
  438. **加载状态处理**
  439. ```javascript
  440. // 在页面或组件中
  441. export default {
  442. data() {
  443. return {
  444. loading: false,
  445. list: [],
  446. error: null,
  447. empty: false
  448. }
  449. },
  450. methods: {
  451. async loadData() {
  452. this.loading = true
  453. this.error = null
  454. try {
  455. const result = await orderApi.getOrderList()
  456. this.list = result.data || []
  457. this.empty = this.list.length === 0
  458. } catch (error) {
  459. this.error = error.message
  460. } finally {
  461. this.loading = false
  462. }
  463. }
  464. }
  465. }
  466. ```
  467. **分页加载模式**
  468. ```javascript
  469. export default {
  470. data() {
  471. return {
  472. list: [],
  473. page: 1,
  474. pageSize: 20,
  475. total: 0,
  476. loading: false,
  477. loadingMore: false,
  478. noMore: false
  479. }
  480. },
  481. methods: {
  482. // 初始加载
  483. async loadData() {
  484. this.page = 1
  485. this.loading = true
  486. try {
  487. const result = await orderApi.getOrderList({
  488. page: this.page,
  489. pageSize: this.pageSize
  490. })
  491. this.list = result.data || []
  492. this.total = result.total || 0
  493. this.noMore = this.list.length >= this.total
  494. } catch (error) {
  495. console.error('加载失败:', error)
  496. } finally {
  497. this.loading = false
  498. }
  499. },
  500. // 加载更多
  501. async loadMore() {
  502. if (this.loadingMore || this.noMore) return
  503. this.page += 1
  504. this.loadingMore = true
  505. try {
  506. const result = await orderApi.getOrderList({
  507. page: this.page,
  508. pageSize: this.pageSize
  509. })
  510. const newData = result.data || []
  511. this.list = [...this.list, ...newData]
  512. this.noMore = this.list.length >= result.total
  513. } catch (error) {
  514. this.page -= 1 // 回滚页码
  515. console.error('加载更多失败:', error)
  516. } finally {
  517. this.loadingMore = false
  518. }
  519. },
  520. // 下拉刷新
  521. async onRefresh() {
  522. await this.loadData()
  523. uni.stopPullDownRefresh()
  524. }
  525. }
  526. }
  527. ```
  528. #### 缓存策略
  529. **数据缓存模式**
  530. ```javascript
  531. // utils/cache.js
  532. export const cache = {
  533. // 设置缓存
  534. set(key, data, expire = 3600000) { // 默认1小时
  535. const item = {
  536. data,
  537. expire: Date.now() + expire
  538. }
  539. uni.setStorageSync(key, JSON.stringify(item))
  540. },
  541. // 获取缓存
  542. get(key) {
  543. try {
  544. const item = JSON.parse(uni.getStorageSync(key) || '{}')
  545. if (item.expire && Date.now() > item.expire) {
  546. uni.removeStorageSync(key)
  547. return null
  548. }
  549. return item.data
  550. } catch {
  551. return null
  552. }
  553. },
  554. // 删除缓存
  555. remove(key) {
  556. uni.removeStorageSync(key)
  557. }
  558. }
  559. // 在 API 中使用缓存
  560. export const userApi = {
  561. async getUserInfo(useCache = true) {
  562. const cacheKey = 'user_info'
  563. if (useCache) {
  564. const cached = cache.get(cacheKey)
  565. if (cached) return cached
  566. }
  567. const result = await request.get('/user/info')
  568. cache.set(cacheKey, result)
  569. return result
  570. }
  571. }
  572. ```
  573. #### 表单处理模式
  574. **表单验证和提交**
  575. ```javascript
  576. export default {
  577. data() {
  578. return {
  579. form: {
  580. name: '',
  581. phone: '',
  582. email: ''
  583. },
  584. rules: {
  585. name: { required: true, message: '请输入姓名' },
  586. phone: { required: true, pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' },
  587. email: { pattern: /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/, message: '请输入正确的邮箱' }
  588. },
  589. submitting: false
  590. }
  591. },
  592. methods: {
  593. // 表单验证
  594. validateForm() {
  595. for (const field in this.rules) {
  596. const rule = this.rules[field]
  597. const value = this.form[field]
  598. if (rule.required && !value) {
  599. uni.showToast({ title: rule.message, icon: 'none' })
  600. return false
  601. }
  602. if (rule.pattern && value && !rule.pattern.test(value)) {
  603. uni.showToast({ title: rule.message, icon: 'none' })
  604. return false
  605. }
  606. }
  607. return true
  608. },
  609. // 提交表单
  610. async submitForm() {
  611. if (!this.validateForm()) return
  612. this.submitting = true
  613. try {
  614. await userApi.updateUser(this.form)
  615. uni.showToast({ title: '保存成功' })
  616. uni.navigateBack()
  617. } catch (error) {
  618. console.error('提交失败:', error)
  619. } finally {
  620. this.submitting = false
  621. }
  622. }
  623. }
  624. }
  625. ```