Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 

324 wiersze
13 KiB

  1. import { defineMixin } from '../vue'
  2. import { deepMerge, $parent, sleep } from '../function/index'
  3. import test from '../function/test'
  4. import route from '../util/route'
  5. import {
  6. applyNativeThemeUI,
  7. applyNativeThemeUIDeferred,
  8. getThemeCardStyle,
  9. getThemeIsDark,
  10. getThemePageStyle,
  11. getThemeVar,
  12. getThemeVarsForStyle,
  13. syncThemeRuntimeFromStorage
  14. } from '../theme/runtime'
  15. // #ifdef APP-NVUE
  16. // 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
  17. const dom = (typeof uni !== 'undefined' && uni && typeof uni.requireNativePlugin === 'function')
  18. ? uni.requireNativePlugin('dom')
  19. : null
  20. // #endif
  21. export const mixin = defineMixin({
  22. // 定义每个组件都可能需要用到的外部样式以及类名
  23. props: {
  24. // 每个组件都有的父组件传递的样式,可以为字符串或者对象形式
  25. customStyle: {
  26. type: [Object, String],
  27. default: () => ({})
  28. },
  29. customClass: {
  30. type: String,
  31. default: ''
  32. },
  33. // 跳转的页面路径
  34. url: {
  35. type: String,
  36. default: ''
  37. },
  38. // 页面跳转的类型
  39. linkType: {
  40. type: String,
  41. default: 'navigateTo'
  42. }
  43. },
  44. data() {
  45. return {
  46. __upPageThemeChangeHandler: null,
  47. upThemeVersion: 0
  48. }
  49. },
  50. onLoad() {
  51. // getRect挂载到$u上,因为这方法需要使用in(this),所以无法把它独立成一个单独的文件导出
  52. this.upBindGetRect()
  53. this.upInitThemeVersion()
  54. if (this.upIsPageScope()) {
  55. this.upApplyNativeThemeUI()
  56. if (typeof uni !== 'undefined' && typeof uni.$on === 'function' && !this.__upPageThemeChangeHandler) {
  57. this.__upPageThemeChangeHandler = () => {
  58. this.upApplyNativeThemeUI()
  59. }
  60. uni.$on('uThemeChange', this.__upPageThemeChangeHandler)
  61. }
  62. }
  63. },
  64. onShow() {
  65. if (this.upIsPageScope()) {
  66. this.upApplyNativeThemeUI()
  67. }
  68. },
  69. created() {
  70. // 组件当中,只有created声明周期,为了能在组件使用,故也在created中将方法挂载到$u
  71. this.upBindGetRect()
  72. this.upInitThemeVersion()
  73. if (typeof uni !== 'undefined' && typeof uni.$on === 'function') {
  74. this.__uThemeChangeHandler = (payload = {}) => {
  75. this.upSyncThemeVersion(payload)
  76. this.upClearUCache()
  77. if (typeof this.$forceUpdate === 'function') {
  78. this.$forceUpdate()
  79. }
  80. }
  81. uni.$on('uThemeChange', this.__uThemeChangeHandler)
  82. }
  83. },
  84. computed: {
  85. // 在2.x版本中,将会把$u挂载到uni对象下,导致在模板中无法使用uni.$u.xxx形式
  86. // 所以这里通过computed计算属性将其附加到this.$u上,就可以在模板或者js中使用uni.$u.xxx
  87. // 只在nvue环境通过此方式引入完整的$u,其他平台会出现性能问题,非nvue则按需引入(主要原因是props过大)
  88. $u() {
  89. this.upThemeVersion
  90. // #ifndef APP-NVUE
  91. const instance = this.$
  92. if (instance?.__upUCache) {
  93. return instance.__upUCache
  94. }
  95. // 在非nvue端,移除props,http,mixin等对象,避免在小程序setData时数据过大影响性能
  96. let mergeU = deepMerge(uni.$u, {
  97. props: undefined,
  98. http: undefined,
  99. mixin: undefined
  100. })
  101. // 缓存结果避免每次计算
  102. if (instance) {
  103. instance.__upUCache = mergeU
  104. return instance.__upUCache
  105. }
  106. return mergeU
  107. // #endif
  108. // #ifdef APP-NVUE
  109. return uni.$u
  110. // #endif
  111. },
  112. upThemeIsDark() {
  113. this.upThemeVersion
  114. return getThemeIsDark(this.$u)
  115. },
  116. upThemeVars() {
  117. this.upThemeVersion
  118. return getThemeVarsForStyle(this.$u)
  119. },
  120. upThemePageStyle() {
  121. this.upThemeVersion
  122. return getThemePageStyle(this.$u)
  123. },
  124. upThemeCardStyle() {
  125. this.upThemeVersion
  126. return getThemeCardStyle(this.$u)
  127. },
  128. /**
  129. * 生成bem规则类名
  130. * 由于微信小程序,H5,nvue之间绑定class的差异,无法通过:class="[bem()]"的形式进行同用
  131. * 故采用如下折中做法,最后返回的是数组(一般平台)或字符串(支付宝和字节跳动平台),类似['a', 'b', 'c']或'a b c'的形式
  132. * @param {String} name 组件名称
  133. * @param {Array} fixed 一直会存在的类名
  134. * @param {Array} change 会根据变量值为true或者false而出现或者隐藏的类名
  135. * @returns {Array|string}
  136. */
  137. bem() {
  138. return function (name, fixed, change) {
  139. // 类名前缀
  140. const prefix = `u-${name}--`
  141. const classes = {}
  142. if (fixed) {
  143. fixed.map((item) => {
  144. // 这里的类名,会一直存在
  145. classes[prefix + this[item]] = true
  146. })
  147. }
  148. if (change) {
  149. change.map((item) => {
  150. // 这里的类名,会根据this[item]的值为true或者false,而进行添加或者移除某一个类
  151. this[item] ? (classes[prefix + item] = this[item]) : (delete classes[prefix + item])
  152. })
  153. }
  154. return Object.keys(classes)
  155. // 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
  156. // #ifdef MP-ALIPAY || MP-TOUTIAO || MP-LARK
  157. .join(' ')
  158. // #endif
  159. }
  160. }
  161. },
  162. methods: {
  163. upClearUCache() {
  164. if (this.$) {
  165. this.$.__upUCache = null
  166. }
  167. },
  168. upBindGetRect() {
  169. const upU = this.$u || (typeof uni !== 'undefined' ? uni.$u : null)
  170. if (upU) {
  171. upU.getRect = this.$uGetRect
  172. } else if (typeof uni !== 'undefined') {
  173. uni.$u = {
  174. getRect: this.$uGetRect
  175. }
  176. }
  177. },
  178. upReadThemeVersion() {
  179. return Number((typeof uni !== 'undefined' && uni.$u && uni.$u.theme && uni.$u.theme.version) || 0)
  180. },
  181. upInitThemeVersion() {
  182. const version = this.upReadThemeVersion()
  183. if (version) {
  184. this.upThemeVersion = version
  185. }
  186. },
  187. upSyncThemeVersion(payload = {}) {
  188. const version = Number(payload.version || this.upReadThemeVersion() || 0)
  189. this.upThemeVersion = version || Number(this.upThemeVersion || 0) + 1
  190. },
  191. upIsPageScope() {
  192. return !!(this.$page || this.route || this.$options?.mpType === 'page')
  193. },
  194. upHasProp(propName) {
  195. const vnodeProps = this.$?.vnode?.props || {}
  196. const kebabName = propName.replace(/[A-Z]/g, (s) => `-${s.toLowerCase()}`)
  197. return Object.prototype.hasOwnProperty.call(vnodeProps, propName)
  198. || Object.prototype.hasOwnProperty.call(vnodeProps, kebabName)
  199. },
  200. upThemeVar(varName, fallbackColor) {
  201. this.upThemeVersion
  202. return getThemeVar(varName, fallbackColor, this.$u)
  203. },
  204. upApplyNativeThemeUI() {
  205. syncThemeRuntimeFromStorage(this.$u)
  206. this.upSyncThemeVersion()
  207. applyNativeThemeUIDeferred(this.$u)
  208. },
  209. // 跳转某一个页面
  210. openPage(urlKey = 'url') {
  211. const url = this[urlKey]
  212. if (url) {
  213. // h5官方回应:发行h5会自动摇树优化,所有使用uni的地方,都会被直接转换成具体的API调用 https://ask.dcloud.net.cn/question/161523?notification_id-1201922__rf-false__item_id-226372
  214. // 使用封装的 route 进行跳转(直接调用方法),不使用 uni 对象
  215. route({ type: this.linkType, url })
  216. // 执行类似uni.navigateTo的方法
  217. // uni[this.linkType]({
  218. // url
  219. // })
  220. }
  221. },
  222. navTo(url = '', linkType = 'navigateTo') {
  223. route({ type: this.linkType, url })
  224. },
  225. // 查询节点信息
  226. // 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸,为支付宝的bug(2020-07-21)
  227. // 解决办法为在组件根部再套一个没有任何作用的view元素
  228. $uGetRect(selector, all) {
  229. return new Promise((resolve) => {
  230. // #ifndef APP-NVUE
  231. uni.createSelectorQuery()
  232. .in(this)[all ? 'selectAll' : 'select'](selector)
  233. .boundingClientRect((rect) => {
  234. if (all && Array.isArray(rect) && rect.length) {
  235. resolve(rect)
  236. }
  237. if (!all && rect) {
  238. resolve(rect)
  239. }
  240. })
  241. .exec()
  242. // #endif
  243. // #ifdef APP-NVUE
  244. sleep(30).then(() => {
  245. let selectorNvue = selector.substring(1) // 去掉开头的#或者.
  246. let selectorRef = this.$refs[selectorNvue]
  247. if (!selectorRef) {
  248. // console.log('不存在元素,请检查是否设置了ref属性' + selectorNvue + '。')
  249. resolve({
  250. with: 0,
  251. height: 0,
  252. left: 0,
  253. right: 0,
  254. top: 0,
  255. bottom: 0
  256. })
  257. }
  258. dom.getComponentRect(selectorRef, res => {
  259. // console.log(res)
  260. resolve(res.size)
  261. })
  262. })
  263. // #endif
  264. })
  265. },
  266. getParentData(parentName = '') {
  267. // 避免在created中去定义parent变量
  268. if (!this.parent) this.parent = {}
  269. // 这里的本质原理是,通过获取父组件实例(也即类似u-radio的父组件u-radio-group的this)
  270. // 将父组件this中对应的参数,赋值给本组件(u-radio的this)的parentData对象中对应的属性
  271. // 之所以需要这么做,是因为所有端中,头条小程序不支持通过this.parent.xxx去监听父组件参数的变化
  272. // 此处并不会自动更新子组件的数据,而是依赖父组件u-radio-group去监听data的变化,手动调用更新子组件的方法去重新获取
  273. this.parent = $parent.call(this, parentName)
  274. if (this.parent.children) {
  275. // 如果父组件的children不存在本组件的实例,才将本实例添加到父组件的children中
  276. this.parent.children.indexOf(this) === -1 && this.parent.children.push(this)
  277. }
  278. if (this.parent && this.parentData) {
  279. // 历遍parentData中的属性,将parent中的同名属性赋值给parentData
  280. Object.keys(this.parentData).map((key) => {
  281. this.parentData[key] = this.parent[key]
  282. })
  283. }
  284. },
  285. // 阻止事件冒泡
  286. preventEvent(e) {
  287. e && typeof (e.stopPropagation) === 'function' && e.stopPropagation()
  288. },
  289. // 空操作
  290. noop(e) {
  291. this.preventEvent(e)
  292. }
  293. },
  294. onReachBottom() {
  295. uni.$emit('uOnReachBottom')
  296. },
  297. beforeUnmount() {
  298. // 判断当前页面是否存在parent和chldren,一般在checkbox和checkbox-group父子联动的场景会有此情况
  299. // 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱
  300. if (this.parent && test.array(this.parent.children)) {
  301. // 组件销毁时,移除父组件中的children数组中对应的实例
  302. const childrenList = this.parent.children
  303. childrenList.map((child, index) => {
  304. // 如果相等,则移除
  305. if (child === this) {
  306. childrenList.splice(index, 1)
  307. }
  308. })
  309. }
  310. if (typeof uni !== 'undefined' && typeof uni.$off === 'function' && this.__uThemeChangeHandler) {
  311. uni.$off('uThemeChange', this.__uThemeChangeHandler)
  312. this.__uThemeChangeHandler = null
  313. }
  314. if (typeof uni !== 'undefined' && typeof uni.$off === 'function' && this.__upPageThemeChangeHandler) {
  315. uni.$off('uThemeChange', this.__upPageThemeChangeHandler)
  316. this.__upPageThemeChangeHandler = null
  317. }
  318. }
  319. })
  320. export default mixin