unicloud-db.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. <template>
  2. <view>
  3. <slot
  4. :options="options"
  5. :data="dataList"
  6. :pagination="paginationInternal"
  7. :loading="loading"
  8. :hasMore="hasMore"
  9. :error="errorMessage"
  10. />
  11. </view>
  12. </template>
  13. <script>
  14. import { onMounted, getCurrentInstance } from 'vue'
  15. import { ssrRef, shallowSsrRef } from '@dcloudio/uni-app'
  16. import { initVueI18n } from '@dcloudio/uni-i18n'
  17. import messages from './i18n/index'
  18. const isArray = Array.isArray
  19. const { t } = initVueI18n(messages)
  20. const events = {
  21. load: 'load',
  22. error: 'error'
  23. }
  24. const pageMode = {
  25. add: 'add',
  26. replace: 'replace'
  27. }
  28. const loadMode = {
  29. auto: 'auto',
  30. onready: 'onready',
  31. manual: 'manual'
  32. }
  33. const attrs = [
  34. 'pageCurrent',
  35. 'pageSize',
  36. 'collection',
  37. 'action',
  38. 'field',
  39. 'getcount',
  40. 'orderby',
  41. 'where',
  42. 'groupby',
  43. 'groupField',
  44. 'distinct'
  45. ]
  46. export default {
  47. name: 'UniClouddb',
  48. // #ifdef VUE3
  49. setup(props) {
  50. // 单条记录时,使用shallowRef(仅支持赋值修改),列表时,采用ref(支持push等修改)
  51. const dataListRef = props.ssrKey ? (props.getone ? shallowSsrRef(undefined, props.ssrKey) : ssrRef([], props.ssrKey)) : (props.getone ? shallowSsrRef(undefined) : ssrRef([]))
  52. const instance = getCurrentInstance()
  53. onMounted(() => {
  54. // client端判断是否需要再次请求数据(正常情况下,SSR返回的html中已包含此数据状态,无需再次额外请求)
  55. if ((!dataListRef.value || dataListRef.value.length === 0) && !props.manual && props.loadtime === loadMode.auto) {
  56. instance.proxy.loadData()
  57. }
  58. })
  59. return { dataList: dataListRef }
  60. },
  61. // 服务端serverPrefetch生命周期,用于服务端加载数据,等将来全端支持Suspense时,可以采用 Suspense + async setup 来实现一版
  62. async serverPrefetch() {
  63. if (!this.manual && this.loadtime === loadMode.auto) {
  64. return this.loadData()
  65. }
  66. },
  67. // #endif
  68. props: {
  69. options: {
  70. type: [Object, Array],
  71. default() {
  72. return {}
  73. }
  74. },
  75. spaceInfo: {
  76. type: Object,
  77. default() {
  78. return {}
  79. }
  80. },
  81. collection: {
  82. type: [String, Array],
  83. default: ''
  84. },
  85. action: {
  86. type: String,
  87. default: ''
  88. },
  89. field: {
  90. type: String,
  91. default: ''
  92. },
  93. orderby: {
  94. type: String,
  95. default: ''
  96. },
  97. where: {
  98. type: [String, Object],
  99. default: ''
  100. },
  101. pageData: {
  102. type: String,
  103. default: 'add'
  104. },
  105. pageCurrent: {
  106. type: Number,
  107. default: 1
  108. },
  109. pageSize: {
  110. type: Number,
  111. default: 20
  112. },
  113. getcount: {
  114. type: [Boolean, String],
  115. default: false
  116. },
  117. getone: {
  118. type: [Boolean, String],
  119. default: false
  120. },
  121. gettree: {
  122. type: [Boolean, String, Object],
  123. default: false
  124. },
  125. gettreepath: {
  126. type: [Boolean, String],
  127. default: false
  128. },
  129. startwith: {
  130. type: String,
  131. default: ''
  132. },
  133. limitlevel: {
  134. type: Number,
  135. default: 10
  136. },
  137. groupby: {
  138. type: String,
  139. default: ''
  140. },
  141. groupField: {
  142. type: String,
  143. default: ''
  144. },
  145. distinct: {
  146. type: [Boolean, String],
  147. default: false
  148. },
  149. pageIndistinct: {
  150. type: [Boolean, String],
  151. default: false
  152. },
  153. foreignKey: {
  154. type: String,
  155. default: ''
  156. },
  157. loadtime: {
  158. type: String,
  159. default: 'auto'
  160. },
  161. manual: {
  162. type: Boolean,
  163. default: false
  164. },
  165. ssrKey: {
  166. type: [String, Number],
  167. default: ""
  168. }
  169. },
  170. data() {
  171. return {
  172. loading: false,
  173. hasMore: false,
  174. // #ifndef VUE3
  175. dataList: [],
  176. // #endif
  177. paginationInternal: {},
  178. errorMessage: ''
  179. }
  180. },
  181. computed: {
  182. collectionArgs () {
  183. return isArray(this.collection) ? this.collection : [this.collection]
  184. },
  185. isLookup () {
  186. return (isArray(this.collection) && this.collection.length > 1) || (typeof this.collection === 'string' && this.collection.indexOf(',') > -1)
  187. },
  188. mainCollection () {
  189. if (typeof this.collection === 'string') {
  190. return this.collection.split(',')[0]
  191. }
  192. const mainQuery = JSON.parse(JSON.stringify(this.collection[0]))
  193. return mainQuery.$db[0].$param[0]
  194. }
  195. },
  196. created() {
  197. this._isEnded = false
  198. this.paginationInternal = {
  199. current: this.pageCurrent,
  200. size: this.pageSize,
  201. count: 0
  202. }
  203. // #ifndef VUE3
  204. if (this.getone) {
  205. this.dataList = undefined
  206. }
  207. // #endif
  208. this.$watch(() => {
  209. var al = []
  210. attrs.forEach(key => {
  211. al.push(this[key])
  212. })
  213. return al
  214. }, (newValue, oldValue) => {
  215. this.paginationInternal.size = this.pageSize
  216. if (newValue[0] !== oldValue[0]) {
  217. this.paginationInternal.current = this.pageCurrent
  218. }
  219. if (this.loadtime === loadMode.manual) {
  220. return
  221. }
  222. let needReset = false
  223. for (let i = 2; i < newValue.length; i++) {
  224. if (newValue[i] !== oldValue[i]) {
  225. needReset = true
  226. break
  227. }
  228. }
  229. if (needReset) {
  230. this.clear()
  231. this.reset()
  232. }
  233. this._execLoadData()
  234. })
  235. // #ifdef MP-TOUTIAO
  236. let changeName
  237. const events = this.$scope.dataset.eventOpts || []
  238. for (var i = 0; i < events.length; i++) {
  239. const event = events[i]
  240. if (event[0].includes('^load')) {
  241. changeName = event[1][0][0]
  242. }
  243. }
  244. if (changeName) {
  245. let parent = this.$parent
  246. let maxDepth = 16
  247. this._changeDataFunction = null
  248. while (parent && maxDepth > 0) {
  249. const fun = parent[changeName]
  250. if (fun && typeof fun === 'function') {
  251. this._changeDataFunction = fun
  252. maxDepth = 0
  253. break
  254. }
  255. parent = parent.$parent
  256. maxDepth--
  257. }
  258. }
  259. // #endif
  260. },
  261. // #ifndef VUE3
  262. mounted() {
  263. if (!this.manual && this.loadtime === loadMode.auto) {
  264. this.loadData()
  265. }
  266. },
  267. // #endif
  268. methods: {
  269. loadData(args1, args2) {
  270. let callback = null
  271. let clear = false
  272. if (typeof args1 === 'object') {
  273. if (args1.clear) {
  274. if (this.pageData === pageMode.replace) {
  275. this.clear()
  276. } else {
  277. clear = args1.clear
  278. }
  279. this.reset()
  280. }
  281. if (args1.current !== undefined) {
  282. this.paginationInternal.current = args1.current
  283. }
  284. if (typeof args2 === 'function') {
  285. callback = args2
  286. }
  287. } else if (typeof args1 === 'function') {
  288. callback = args1
  289. }
  290. return this._execLoadData(callback, clear)
  291. },
  292. loadMore() {
  293. if (this._isEnded || this.loading) {
  294. return
  295. }
  296. if (this.pageData === pageMode.add) {
  297. this.paginationInternal.current++
  298. }
  299. this._execLoadData()
  300. },
  301. refresh() {
  302. this.clear()
  303. this._execLoadData()
  304. },
  305. clear() {
  306. this._isEnded = false
  307. this.dataList = []
  308. },
  309. reset() {
  310. this.paginationInternal.current = 1
  311. },
  312. add(value, {
  313. action,
  314. showToast = true,
  315. toastTitle,
  316. success,
  317. fail,
  318. complete,
  319. needConfirm = true,
  320. needLoading = true,
  321. loadingTitle = ''
  322. } = {}) {
  323. if (needLoading) {
  324. uni.showLoading({
  325. title: loadingTitle
  326. })
  327. }
  328. /* eslint-disable no-undef */
  329. let db = uniCloud.database(this.spaceInfo)
  330. if (action) {
  331. db = db.action(action)
  332. }
  333. db.collection(this.mainCollection).add(value).then((res) => {
  334. success && success(res)
  335. if (showToast) {
  336. uni.showToast({
  337. title: toastTitle || t('uniCloud.component.add.success')
  338. })
  339. }
  340. }).catch((err) => {
  341. fail && fail(err)
  342. if (needConfirm) {
  343. uni.showModal({
  344. content: err.message,
  345. showCancel: false
  346. })
  347. }
  348. }).finally(() => {
  349. if (needLoading) {
  350. uni.hideLoading()
  351. }
  352. complete && complete()
  353. })
  354. },
  355. remove(id, {
  356. action,
  357. success,
  358. fail,
  359. complete,
  360. confirmTitle,
  361. confirmContent,
  362. needConfirm = true,
  363. needLoading = true,
  364. loadingTitle = ''
  365. } = {}) {
  366. if (!id || !id.length) {
  367. return
  368. }
  369. if (!needConfirm) {
  370. this._execRemove(id, action, success, fail, complete, needConfirm, needLoading, loadingTitle)
  371. return
  372. }
  373. uni.showModal({
  374. title: confirmTitle || t('uniCloud.component.remove.showModal.title'),
  375. content: confirmContent || t('uniCloud.component.remove.showModal.content'),
  376. showCancel: true,
  377. success: (res) => {
  378. if (!res.confirm) {
  379. return
  380. }
  381. this._execRemove(id, action, success, fail, complete, needConfirm, needLoading, loadingTitle)
  382. }
  383. })
  384. },
  385. update(id, value, {
  386. action,
  387. showToast = true,
  388. toastTitle,
  389. success,
  390. fail,
  391. complete,
  392. needConfirm = true,
  393. needLoading = true,
  394. loadingTitle = ''
  395. } = {}) {
  396. if (needLoading) {
  397. uni.showLoading({
  398. title: loadingTitle
  399. })
  400. }
  401. let db = uniCloud.database(this.spaceInfo)
  402. if (action) {
  403. db = db.action(action)
  404. }
  405. return db.collection(this.mainCollection).doc(id).update(value).then((res) => {
  406. success && success(res)
  407. if (showToast) {
  408. uni.showToast({
  409. title: toastTitle || t('uniCloud.component.update.success')
  410. })
  411. }
  412. }).catch((err) => {
  413. fail && fail(err)
  414. if (needConfirm) {
  415. uni.showModal({
  416. content: err.message,
  417. showCancel: false
  418. })
  419. }
  420. }).finally(() => {
  421. if (needLoading) {
  422. uni.hideLoading()
  423. }
  424. complete && complete()
  425. })
  426. },
  427. getTemp(isTemp = true) {
  428. let db = uniCloud.database(this.spaceInfo)
  429. if (this.action) {
  430. db = db.action(this.action)
  431. }
  432. db = db.collection(...this.collectionArgs)
  433. if (this.foreignKey) {
  434. db = db.foreignKey(this.foreignKey)
  435. }
  436. if (!(!this.where || !Object.keys(this.where).length)) {
  437. db = db.where(this.where)
  438. }
  439. if (this.field) {
  440. db = db.field(this.field)
  441. }
  442. if (this.groupby) {
  443. db = db.groupBy(this.groupby)
  444. }
  445. if (this.groupField) {
  446. db = db.groupField(this.groupField)
  447. }
  448. if (this.distinct === true) {
  449. db = db.distinct()
  450. }
  451. if (this.orderby) {
  452. db = db.orderBy(this.orderby)
  453. }
  454. const {
  455. current,
  456. size
  457. } = this.paginationInternal
  458. const getOptions = {}
  459. if (this.getcount) {
  460. getOptions.getCount = this.getcount
  461. }
  462. const treeOptions = {
  463. limitLevel: this.limitlevel,
  464. startWith: this.startwith
  465. }
  466. if (this.gettree) {
  467. getOptions.getTree = treeOptions
  468. }
  469. if (this.gettreepath) {
  470. getOptions.getTreePath = treeOptions
  471. }
  472. db = db.skip(size * (current - 1)).limit(size)
  473. if (isTemp) {
  474. db = db.getTemp(getOptions)
  475. db.udb = this
  476. } else {
  477. db = db.get(getOptions)
  478. }
  479. return db
  480. },
  481. setResult(result) {
  482. if (result.code === 0) {
  483. this._execLoadDataSuccess(result)
  484. } else {
  485. this._execLoadDataFail(new Error(result.message))
  486. }
  487. },
  488. _execLoadData(callback, clear) {
  489. if (this.loading) {
  490. return
  491. }
  492. this.loading = true
  493. this.errorMessage = ''
  494. return this._getExec().then((res) => {
  495. this.loading = false
  496. this._execLoadDataSuccess(res.result, callback, clear)
  497. }).catch((err) => {
  498. this.loading = false
  499. this._execLoadDataFail(err, callback)
  500. })
  501. },
  502. _execLoadDataSuccess(result, callback, clear) {
  503. const {
  504. data,
  505. count
  506. } = result
  507. this._isEnded = count !== undefined ? (this.paginationInternal.current * this.paginationInternal.size >= count) : (data.length < this.pageSize)
  508. this.hasMore = !this._isEnded
  509. const data2 = this.getone ? (data.length ? data[0] : undefined) : data
  510. if (this.getcount) {
  511. this.paginationInternal.count = count
  512. }
  513. callback && callback(data2, this._isEnded, this.paginationInternal)
  514. this._dispatchEvent(events.load, data2)
  515. if (this.getone || this.pageData === pageMode.replace) {
  516. this.dataList = data2
  517. } else {
  518. if (clear) {
  519. this.dataList = data2
  520. } else {
  521. this.dataList.push(...data2)
  522. }
  523. }
  524. },
  525. _execLoadDataFail(err, callback) {
  526. this.errorMessage = err
  527. callback && callback()
  528. this.$emit(events.error, err)
  529. if (process.env.NODE_ENV === 'development') {
  530. console.error(err)
  531. }
  532. },
  533. _getExec() {
  534. return this.getTemp(false)
  535. },
  536. _execRemove(id, action, success, fail, complete, needConfirm, needLoading, loadingTitle) {
  537. if (!this.collection || !id) {
  538. return
  539. }
  540. const ids = isArray(id) ? id : [id]
  541. if (!ids.length) {
  542. return
  543. }
  544. if (needLoading) {
  545. uni.showLoading({
  546. mask: true,
  547. title: loadingTitle
  548. })
  549. }
  550. const db = uniCloud.database(this.spaceInfo)
  551. const dbCmd = db.command
  552. let exec = db
  553. if (action) {
  554. exec = exec.action(action)
  555. }
  556. exec.collection(this.mainCollection).where({
  557. _id: dbCmd.in(ids)
  558. }).remove().then((res) => {
  559. success && success(res.result)
  560. if (this.pageData === pageMode.replace) {
  561. this.refresh()
  562. } else {
  563. this.removeData(ids)
  564. }
  565. }).catch((err) => {
  566. fail && fail(err)
  567. if (needConfirm) {
  568. uni.showModal({
  569. content: err.message,
  570. showCancel: false
  571. })
  572. }
  573. }).finally(() => {
  574. if (needLoading) {
  575. uni.hideLoading()
  576. }
  577. complete && complete()
  578. })
  579. },
  580. removeData(ids) {
  581. const il = ids.slice(0)
  582. const dl = this.dataList
  583. for (let i = dl.length - 1; i >= 0; i--) {
  584. const index = il.indexOf(dl[i]._id)
  585. if (index >= 0) {
  586. dl.splice(i, 1)
  587. il.splice(index, 1)
  588. }
  589. }
  590. },
  591. _dispatchEvent(type, data) {
  592. if (this._changeDataFunction) {
  593. this._changeDataFunction(data, this._isEnded, this.paginationInternal)
  594. } else {
  595. this.$emit(type, data, this._isEnded, this.paginationInternal)
  596. }
  597. }
  598. }
  599. }
  600. </script>