SimpleList.vue 7.8 KB

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