FlexView.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. <template>
  2. <view
  3. :id="innerId"
  4. :class="[
  5. 'nana-flex-layout', {
  6. 'nana-flex-row': direction === 'row',
  7. 'nana-flex-column': direction === 'column'
  8. },
  9. innerClass,
  10. ]"
  11. :style="finalStyle"
  12. @mouseenter="handleMouseEnter"
  13. @mouseleave="handleMouseLeave"
  14. @mousedown="handleTouchStart"
  15. @mouseup="handleTouchEnd"
  16. @touchstart="handleTouchStart"
  17. @touchend="handleTouchEnd"
  18. @click="handleClick"
  19. >
  20. <slot></slot>
  21. </view>
  22. </template>
  23. <script setup lang="ts">
  24. /**
  25. * 组件说明:Flex组件,用于一些布局中快速写容器,是一系列盒子的基础组件。
  26. */
  27. import { computed, onMounted, ref } from 'vue';
  28. import { useTheme, type ThemePaddingMargin } from '../theme/ThemeDefine';
  29. import { configMargin, configPadding } from '../theme/ThemeTools';
  30. export type FlexDirection = "row"|"column"|'row-reverse'|'column-reverse';
  31. export type FlexJustifyType = 'flex-start' | 'flex-end' | 'center' |'space-between' |'space-around' |'space-evenly';
  32. export type FlexAlignType = "stretch"|'center'|'start'|'end'|'flex-start' | 'flex-end' | 'center';
  33. export type StateType = 'default' | 'active' | 'pressed';
  34. export interface FlexProps {
  35. innerId?: string,
  36. /**
  37. * 盒子定位
  38. */
  39. position?: "absolute" | "relative",
  40. /**
  41. * 弹性盒子方向
  42. */
  43. direction?: FlexDirection,
  44. /**
  45. * 子元素在主轴上的对齐方式
  46. */
  47. justify?: FlexJustifyType,
  48. /**
  49. * 子元素在交叉轴上的对齐方式
  50. */
  51. align?: FlexAlignType|"auto",
  52. /**
  53. * 当前元素在主轴上的对齐方式
  54. */
  55. alignSelf?: FlexAlignType|"auto",
  56. /**
  57. * 主轴与交叉轴是否居中
  58. */
  59. center?: boolean,
  60. /**
  61. * 弹性布局是否换行
  62. */
  63. wrap?: boolean,
  64. /**
  65. * 特殊样式
  66. */
  67. innerStyle?: object,
  68. /**
  69. * 特殊类名
  70. */
  71. innerClass?: string|string[]|object,
  72. /**
  73. * flex 参数
  74. */
  75. flex?: number|string,
  76. /**
  77. * flexBasis 参数
  78. */
  79. flexBasis?: number|string,
  80. /**
  81. * flexGrow 参数
  82. */
  83. flexGrow?: number,
  84. /**
  85. * flexShrink 参数
  86. */
  87. flexShrink?: number,
  88. /**
  89. * 内边距参数(支持数字或数组)
  90. */
  91. padding?: number|Array<number>|ThemePaddingMargin,
  92. /**
  93. * 外边距参数(支持数字或数组)
  94. */
  95. margin?: number|Array<number>|ThemePaddingMargin,
  96. /**
  97. * 位置参数
  98. */
  99. top?: number|string,
  100. right?: number|string,
  101. bottom?: number|string,
  102. left?: number|string,
  103. /**
  104. * 圆角
  105. */
  106. radius?: number|string,
  107. /**
  108. * 间距
  109. */
  110. gap?: number|string,
  111. /**
  112. * 是否可以点击
  113. */
  114. touchable?: boolean,
  115. /**
  116. * 背景颜色
  117. */
  118. backgroundColor?: string,
  119. /**
  120. * 按下时的颜色
  121. */
  122. pressedColor?: string,
  123. /**
  124. * 按下时的透明度(仅在 pressedColor 未设置时有效)
  125. */
  126. activeOpacity?: number,
  127. /**
  128. * 宽度
  129. */
  130. width?: number|string,
  131. /**
  132. * 高度
  133. */
  134. height?: number|string,
  135. overflow?: 'visible'|'hidden'|'scroll'|'auto'
  136. }
  137. const props = withDefaults(defineProps<FlexProps>(), {
  138. direction: "column",
  139. backgroundColor: '',
  140. activeOpacity: 0.7,
  141. touchable: false,
  142. });
  143. const themeContext = useTheme();
  144. const commonStyle = computed(() => {
  145. const obj : Record<string, any> = {
  146. flexDirection: props.direction,
  147. flexBasis: props.flexBasis,
  148. flexGrow: props.flexGrow,
  149. flexShrink: props.flexShrink,
  150. justifyContent: props.center ? (props.justify || 'center') : props.justify,
  151. alignItems: props.center ? (props.align || 'center') : props.align,
  152. position: props.position,
  153. alignSelf: props.alignSelf,
  154. flexWrap: props.wrap ? 'wrap' : 'nowrap',
  155. backgroundColor: themeContext.resolveThemeColor(props.backgroundColor),
  156. width: themeContext.resolveThemeSize(props.width),
  157. height: themeContext.resolveThemeSize(props.height),
  158. gap: themeContext.resolveThemeSize(props.gap),
  159. borderRadius: themeContext.resolveThemeSize(props.radius),
  160. overflow: props.overflow,
  161. ...(props.innerStyle ? props.innerStyle : {}),
  162. }
  163. //内边距样式
  164. configPadding(obj, themeContext.theme, props.padding as any);
  165. //外边距样式
  166. configMargin(obj, themeContext.theme, props.margin as any);
  167. if (obj.paddingVertical) {
  168. if (obj.paddingTop === undefined)
  169. obj.paddingTop = obj.paddingVertical;
  170. if (obj.paddingBottom === undefined)
  171. obj.paddingBottom = obj.paddingVertical;
  172. obj.paddingVertical = undefined;
  173. }
  174. if (obj.paddingHorizontal) {
  175. if (obj.paddingLeft === undefined)
  176. obj.paddingLeft = obj.paddingHorizontal;
  177. if (obj.paddingRight === undefined)
  178. obj.paddingRight = obj.paddingHorizontal;
  179. obj.paddingHorizontal = undefined;
  180. }
  181. if (obj.marginVertical) {
  182. obj.marginTop = obj.marginVertical;
  183. obj.marginBottom = obj.marginVertical;
  184. obj.marginVertical = undefined;
  185. }
  186. if (obj.marginHorizontal) {
  187. obj.marginLeft = obj.marginHorizontal;
  188. obj.marginRight = obj.marginHorizontal;
  189. obj.marginHorizontal = undefined;
  190. }
  191. //绝对距样式
  192. if (typeof props.left !== 'undefined')
  193. obj.left = themeContext.resolveThemeSize(props.left);
  194. if (typeof props.right !== 'undefined')
  195. obj.right = themeContext.resolveThemeSize(props.right);
  196. if (typeof props.top !== 'undefined')
  197. obj.top = themeContext.resolveThemeSize(props.top);
  198. if (typeof props.bottom !== 'undefined')
  199. obj.bottom = themeContext.resolveThemeSize(props.bottom);
  200. if (typeof props.flex !== 'undefined')
  201. obj.flex = props.flex;
  202. return obj
  203. });
  204. const finalStyle = computed(() => {
  205. const obj : Record<string, any> = {};
  206. if (props.pressedColor != undefined) {
  207. if (isPressed.value)
  208. obj.backgroundColor = themeContext.resolveThemeColor(props.pressedColor);
  209. } else if (props.activeOpacity != undefined)
  210. obj.opacity = isPressed.value ? props.activeOpacity : 1;
  211. const o = {
  212. ...commonStyle.value,
  213. ...obj
  214. }
  215. for (const key in o) {
  216. if (o[key] === undefined)
  217. delete o[key];
  218. }
  219. return o;
  220. })
  221. defineOptions({
  222. options: {
  223. styleIsolation: "shared",
  224. virtualHost: true
  225. }
  226. })
  227. const emit = defineEmits([ "click", "state" ]);
  228. const isPressed = ref(false)
  229. function handleTouchStart() {
  230. if (props.touchable) {
  231. isPressed.value = true; // 按下时改变状态
  232. emit('state', 'active');
  233. }
  234. }
  235. function handleMouseEnter() {
  236. if (props.touchable)
  237. emit('state', 'active')
  238. }
  239. function handleMouseLeave() {
  240. if (props.touchable)
  241. emit('state', 'default')
  242. }
  243. function handleClick(e: Event) {
  244. if (props.touchable)
  245. emit('click', e);
  246. }
  247. function handleTouchEnd() {
  248. if (props.touchable) {
  249. isPressed.value = false; // 释放时恢复状态
  250. emit('state', 'default');
  251. }
  252. }
  253. onMounted(() => {
  254. emit('state', 'default')
  255. })
  256. </script>
  257. <style>
  258. .nana-flex-layout {
  259. display: flex;
  260. }
  261. .nana-flex-row {
  262. flex-direction: row;
  263. }
  264. .nana-flex-column {
  265. flex-direction: column;
  266. }
  267. </style>