SimpleList.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <template>
  2. <FixedVirtualList
  3. v-if="virtual"
  4. :data="data"
  5. :dataKey="dataKey"
  6. :containerStyle="innerStyle"
  7. :itemSize="virtualItemHeight"
  8. :bufferSize="2"
  9. v-bind="$attrs"
  10. >
  11. <template #item="{ item, index }">
  12. <SimpleListItem
  13. :item="item"
  14. :index="index"
  15. :dataDisplayProp="dataDisplayProp"
  16. :colorProp="colorProp"
  17. :disabledProp="disabledProp"
  18. :showCheck="mode !== 'select'"
  19. :checked="checkedList.indexOf(item) >= 0"
  20. :overrideItem="Boolean($slots.itemContent)"
  21. @click="onItemPress(item, index)"
  22. >
  23. <template v-if="$slots.itemContent" #itemContent>
  24. <slot name="itemContent" :item="item" :index="index" />
  25. </template>
  26. </SimpleListItem>
  27. </template>
  28. <template #empty>
  29. <slot name="empty">
  30. <Empty :description="emptyText" />
  31. </slot>
  32. </template>
  33. <template #inner>
  34. <slot name="inner" />
  35. </template>
  36. </FixedVirtualList>
  37. <scroll-view v-else :scroll-y="true" :scroll-with-animation="true" :style="{
  38. width: innerStyle?.width,
  39. height: innerStyle?.height,
  40. }">
  41. <FlexCol :innerStyle="innerStyle">
  42. <SimpleListItem
  43. v-for="(item, index) in data"
  44. :key="dataKey ? (item as Record<string, any>)[dataKey] : index"
  45. :item="item"
  46. :index="index"
  47. :dataDisplayProp="dataDisplayProp"
  48. :colorProp="colorProp"
  49. :disabledProp="disabledProp"
  50. :showCheck="mode !== 'select'"
  51. :checked="checkedList.indexOf(item) >= 0"
  52. :overrideItem="Boolean($slots.itemContent)"
  53. @click="onItemPress(item, index)"
  54. >
  55. <template v-if="$slots.itemContent" #itemContent>
  56. <slot name="itemContent" :item="item" :index="index" />
  57. </template>
  58. </SimpleListItem>
  59. <slot v-if="data.length == 0" name="empty">
  60. <Empty :description="emptyText" />
  61. </slot>
  62. <slot name="inner" />
  63. </FlexCol>
  64. </scroll-view>
  65. </template>
  66. <script setup lang="ts" generic="T">
  67. import { computed, nextTick, provide, ref, watch, type ComputedRef, type Ref } from 'vue';
  68. import { useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
  69. import { DynamicColor, DynamicSize, DynamicSize2, DynamicVar } from '../theme/ThemeTools';
  70. import type { CheckBoxDefaultButtonProps } from '../form/CheckBoxDefaultButton.vue';
  71. import FlexCol from '../layout/FlexCol.vue';
  72. import Empty from '../feedback/Empty.vue';
  73. import FixedVirtualList from './FixedVirtualList.vue';
  74. import SimpleListItem from './SimpleListItem.vue';
  75. export interface SimpleListProps<T> {
  76. /**
  77. * 是否使用虚拟列表
  78. * @default false
  79. */
  80. virtual?: boolean;
  81. /**
  82. * 虚拟列表条目高度px
  83. * @default 40
  84. */
  85. virtualItemHeight?: number;
  86. /**
  87. * 条目的自定义样式
  88. */
  89. itemStyle?: ViewStyle;
  90. /**
  91. * 条目文字的自定义样式
  92. */
  93. textStyle?: TextStyle;
  94. /**
  95. * 选中的条目的自定义样式
  96. */
  97. checkedItemStyle?: ViewStyle;
  98. /**
  99. * 选中的条目文字的自定义样式
  100. */
  101. checkedTextStyle?: TextStyle;
  102. /**
  103. * 空数据时显示的文字
  104. */
  105. emptyText?: string,
  106. /**
  107. * 源数据
  108. */
  109. data: T[],
  110. /**
  111. * 数据项的唯一键名,用于vue循环优化
  112. */
  113. dataKey?: string,
  114. /**
  115. * 显示数据的prop,如果为空,则尝试直接把数据当 string 显示。
  116. */
  117. dataDisplayProp?: string,
  118. /**
  119. * 控制数据条目颜色的字段名称,为空则使用默认颜色。
  120. */
  121. colorProp?: string,
  122. /**
  123. * 控制是否禁用数据条目的字段名称,为空则不禁用。
  124. */
  125. disabledProp?: string,
  126. /**
  127. * 列表的选择模式
  128. *
  129. * * select 点击选择模式
  130. * * single-check 单选选择模式,条目右边有选择框
  131. * * mulit-check 多选选择模式,条目右边有选择框
  132. * @default 'select'
  133. */
  134. mode?: 'select'|'single-check'|'mulit-check',
  135. /**
  136. * 当列表显示选择框时,选择框的自定义属性
  137. * @default {
  138. * borderColor: 'border.cell',
  139. * checkColor: 'white',
  140. * color: 'primary',
  141. * size: 20,
  142. * iconSize: mode === 'single-check' ? 10 : 16,
  143. * type: mode === 'single-check' ? 'radio' : 'icon',
  144. * disabled: false,
  145. * }
  146. */
  147. checkProps?: CheckBoxDefaultButtonProps,
  148. /**
  149. * 当用使用选择框模式时,默认选中条目
  150. * @default []
  151. */
  152. checkedItems?: T[],
  153. innerStyle?: ViewStyle;
  154. }
  155. export interface SimpleListContext {
  156. itemStyle: ComputedRef<ViewStyle>;
  157. textStyle: ComputedRef<TextStyle>;
  158. checkedItemStyle: ComputedRef<ViewStyle>;
  159. checkedTextStyle: ComputedRef<TextStyle>;
  160. checkProps: ComputedRef<CheckBoxDefaultButtonProps>;
  161. pressedColor: ComputedRef<string>;
  162. }
  163. const props = withDefaults(defineProps<SimpleListProps<T>>(), {
  164. mode: 'select',
  165. virtual: false,
  166. virtualItemHeight: 40,
  167. });
  168. const emit = defineEmits([ 'itemClick', 'selectedItemChanged' ]);
  169. const themeContext = useTheme();
  170. const themeStyles = themeContext.useThemeStyles({
  171. item: {
  172. padding: DynamicSize2('SimpleListItemPaddingVertical', 'SimpleListItemPaddingHorizontal', 20, 35),
  173. fontSize: DynamicSize('SimpleListItemFontSize', 28),
  174. borderTopStyle: 'solid',
  175. borderTopWidth: DynamicSize('SimpleListItemBorderTopWidth', 0),
  176. borderTopColor: DynamicColor('SimpleListItemBorderColor', 'border.cell'),
  177. borderBottomStyle: 'solid',
  178. borderBottomWidth: DynamicSize('SimpleListItemBorderBottomWidth', 2),
  179. borderBottomColor: DynamicColor('SimpleListItemBorderColor', 'border.cell'),
  180. color: DynamicColor('SimpleListItemColor', 'text.content'),
  181. backgroundColor: DynamicColor('SimpleListItemBackgroundColor', 'white'),
  182. flexDirection: 'row',
  183. },
  184. itemText: {
  185. flex: 1,
  186. fontSize: DynamicSize('SimpleListItemTextFontSize', 28),
  187. fontWeight: DynamicSize('SimpleListItemTextFontWeight', 'normal'),
  188. color: DynamicColor('SimpleListItemTextColor', 'text.content'),
  189. },
  190. itemTextChecked: {
  191. fontSize: DynamicSize('SimpleListItemCheckedTextFontSize', 28),
  192. fontWeight: DynamicVar('SimpleListItemCheckedTextFontWeight', 'bold'),
  193. color: DynamicColor('SimpleListItemCheckedTextColor', 'primary'),
  194. },
  195. });
  196. const itemStyle = computed(() => ({
  197. ...themeStyles.item.value,
  198. ...props.itemStyle,
  199. } as ViewStyle));
  200. const textStyle = computed(() => ({
  201. ...themeStyles.itemText.value,
  202. ...props.textStyle,
  203. } as ViewStyle));
  204. const checkedItemStyle = computed(() => ({
  205. ...themeStyles.item.value,
  206. ...props.checkedItemStyle,
  207. } as ViewStyle));
  208. const checkedTextStyle = computed(() => ({
  209. ...themeStyles.itemText.value,
  210. ...themeStyles.itemTextChecked.value,
  211. ...props.checkedTextStyle,
  212. } as ViewStyle));
  213. const checkProps = computed(() => ({
  214. borderColor: 'border.cell',
  215. checkColor: 'white',
  216. color: 'primary',
  217. size: 40,
  218. iconSize: props.mode === 'single-check' ? 20 : 32,
  219. type: props.mode === 'single-check' ? 'radio' : 'icon',
  220. disabled: false,
  221. ...props.checkProps,
  222. } as CheckBoxDefaultButtonProps));
  223. const pressedColor = computed(() => {
  224. return themeContext.resolveThemeColor('SimpleListItemPressedColor', 'pressed.white')!;
  225. });
  226. provide<SimpleListContext>('SimpleListContext', {
  227. itemStyle,
  228. textStyle,
  229. checkedItemStyle,
  230. checkedTextStyle,
  231. checkProps,
  232. pressedColor,
  233. });
  234. const checkedList = ref(props.checkedItems || []) as Ref<T[]>;
  235. watch(() => props.checkedItems, (val) => {
  236. checkedList.value = val || [];
  237. });
  238. function onItemPress(item: T, index: number) {
  239. if (props.mode === 'single-check') {
  240. checkedList.value = ([ item ]);
  241. emit('selectedItemChanged', [ item ]);
  242. } else if (props.mode === 'mulit-check') {
  243. checkedList.value = ((prev) => {
  244. let arr : any[];
  245. if (prev.indexOf(item) >= 0)
  246. arr = prev.filter(k => {
  247. if (props.dataKey)
  248. return (k as any)[props.dataKey] !== (item as any)[props.dataKey];
  249. return k !== item;
  250. });
  251. else
  252. arr = prev.concat([ item ]);
  253. nextTick(() => {
  254. emit('selectedItemChanged', arr);
  255. console.log('selectedItemChanged', arr);
  256. });
  257. return arr;
  258. })(checkedList.value);
  259. }
  260. emit('itemClick', item, index);
  261. }
  262. </script>