styles.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <template>
  2. <div class="hot-styles">
  3. <div class="add item" v-if="!props.all && styles.length < maxLength">
  4. <span class="fun-ctrl">
  5. <ui-input
  6. class="input"
  7. preview
  8. accept=".jpg, .jpeg, .png"
  9. @update:modelValue="iconUpload"
  10. type="file"
  11. >
  12. <template v-slot:replace>
  13. <ui-icon type="add" class="icon" />
  14. </template>
  15. </ui-input>
  16. </span>
  17. </div>
  18. <div
  19. v-for="hotStyle in styleAll"
  20. class="item"
  21. :class="{ active: active === hotStyle }"
  22. @click="clickHandler(hotStyle)"
  23. >
  24. <span>
  25. <img :src="getFileUrl(hotStyle.icon)" />
  26. <ui-icon
  27. v-if="!hotStyle.default"
  28. class="delete"
  29. type="close"
  30. @click.stop="emit('delete', hotStyle)"
  31. />
  32. </span>
  33. </div>
  34. <div
  35. v-if="!props.all && props.styles.length > maxShowLen"
  36. class="add item style-more"
  37. @click="showAll = !showAll"
  38. >
  39. <span class="fun-ctrl">
  40. <ui-icon :type="showAll ? 'pull-up' : 'pull-down'" class="icon" />
  41. <ui-bubble
  42. class="more-content"
  43. :show="showAll"
  44. @click.stop
  45. type="bottom">
  46. <styles
  47. :styles="styles.filter(style => !styleAll.includes(style))"
  48. :active="active"
  49. all
  50. @quitMore="showAll = false"
  51. @uploadStyles="(style: TaggingStyle) => emit('uploadStyle', style)"
  52. @change="clickHandler"
  53. @delete="(style: TaggingStyle) => emit('delete', style)"
  54. />
  55. </ui-bubble>
  56. </span>
  57. </div>
  58. </div>
  59. <!-- <div class="buttons" v-if="props.all">
  60. <ui-button type="submit" class="button" @click="emit('quitMore')">取消</ui-button>
  61. <ui-button type="primary" class="button" @click="enterHandler">保存</ui-button>
  62. </div> -->
  63. </template>
  64. <script setup lang="ts">
  65. import { TaggingStyle, TaggingStyles } from '@/store'
  66. import { createTaggingStyle } from '@/store'
  67. import { ref, computed, defineEmits } from 'vue'
  68. import { Cropper } from 'bill/index'
  69. import { getFileUrl } from '@/utils'
  70. const props = defineProps<{
  71. styles: TaggingStyles
  72. active: TaggingStyle
  73. all?: boolean
  74. }>()
  75. const maxLength = 40
  76. const maxShowLen = computed(() => props.styles.length < maxLength ? 5 : 6)
  77. const emit = defineEmits<{
  78. (e: 'change', style: TaggingStyle): void
  79. (e: 'delete', style: TaggingStyle): void
  80. (e: 'uploadStyle', styles: TaggingStyle): void
  81. (e: 'quitMore'): void
  82. }>()
  83. const showAll = ref(false)
  84. const styleAll = computed(() => {
  85. if (props.all) {
  86. return props.styles
  87. } else {
  88. const styles = props.styles.slice(0, props.styles.length > maxShowLen.value ? maxShowLen.value : maxShowLen.value + 1)
  89. if (!styles.includes(props.active) && props.active) {
  90. styles[styles.length - 1] = props.active
  91. }
  92. return styles
  93. }
  94. })
  95. const iconUpload = async ({ file, preview }: { file: File, preview: string }) => {
  96. const data = await Cropper.open(preview)
  97. if (data) {
  98. emit('uploadStyle', createTaggingStyle({
  99. name: file.name,
  100. icon: { url: data[1], blob: data[0] }
  101. }))
  102. }
  103. }
  104. const clickHandler = (hotStyle: TaggingStyle) => {
  105. if (!props.all) {
  106. showAll.value = false
  107. }
  108. emit('change', hotStyle)
  109. }
  110. </script>
  111. <style lang="scss" scoped>
  112. .hot-styles {
  113. --size: 40px;
  114. --icon-size: calc(var(--size) * 0.85);
  115. margin: 24px 0;
  116. display: grid;
  117. grid-template-columns: repeat(auto-fill, minmax(var(--size), 1fr));
  118. gap: calc(var(--size) / 4);
  119. align-items: start;
  120. justify-content: center;
  121. .item {
  122. --un-active-color: rgba(var(--colors-primary-base-fill), 0);
  123. --active-transition: .3s ease;
  124. cursor: pointer;
  125. &.disable {
  126. opacity: .3;
  127. pointer-events: none;
  128. cursor: inherit;
  129. }
  130. span {
  131. width: var(--size);
  132. height: var(--size);
  133. display: flex;
  134. align-items: center;
  135. justify-content: center;
  136. border: 1px solid var(--un-active-color);
  137. position: relative;
  138. transition: border var(--active-transition);
  139. border-radius: 4px;
  140. .input {
  141. margin: 0;
  142. }
  143. img {
  144. width: var(--icon-size);
  145. height: var(--icon-size);
  146. // outline: 1px dashed var(--un-active-color);
  147. transition: outline-color var(--active-transition);
  148. border-radius: 4px;
  149. }
  150. .delete {
  151. --round-size: calc(var(--size) * 0.45);
  152. position: absolute;
  153. width: var(--round-size);
  154. height: var(--round-size);
  155. border-radius: 50%;
  156. background-color: rgba(250, 63, 72, 1);
  157. right: calc(var(--round-size) * -1 / 2);
  158. top: calc(var(--round-size) * -1 / 2);
  159. transition: background-color var(--active-transition);
  160. font-size: 12px;
  161. display: flex;
  162. align-items: center;
  163. justify-content: center;
  164. color: #fff;
  165. opacity: 0;
  166. transition: opacity .3s ease;
  167. }
  168. }
  169. p {
  170. transition: color var(--active-transition);
  171. margin-top: calc(var(--size) / 4);
  172. text-align: center;
  173. color: rgb(var(--colors-primary-fill));
  174. font-size: var(--small-size);
  175. }
  176. &.active {
  177. color: rgba(var(--colors-primary-base-fill), 1);
  178. --un-active-color: rgba(var(--colors-primary-base-fill), 1);
  179. span img {
  180. outline-color: rgb(var(--colors-primary-fill));
  181. }
  182. p {
  183. color: currentColor;
  184. }
  185. }
  186. &:not(.style-more):hover {
  187. .delete {
  188. opacity: 0.5;
  189. &:hover {
  190. opacity: 1;
  191. }
  192. }
  193. }
  194. }
  195. .add {
  196. height: 100%;
  197. align-items: center;
  198. display: flex;
  199. flex: none;
  200. span {
  201. font-size: calc(var(--icon-size) * 0.4);
  202. border: none;
  203. &::before {
  204. content: '';
  205. position: absolute;
  206. left: 50%;
  207. top: 50%;
  208. transform: translate(-50%, -50%);
  209. width: var(--icon-size);
  210. height: var(--icon-size);
  211. border-radius: 2px;
  212. border: 1px solid var(--colors-border-color);
  213. transition: border-color .3s ease;
  214. }
  215. &:hover::before {
  216. border-color: rgba(255,255,255,1);
  217. }
  218. &:active::before {
  219. border-color: var(--colors-primary-base) !important;
  220. }
  221. }
  222. }
  223. .style-more {
  224. .fun-ctrl {
  225. position: relative;
  226. }
  227. .more-content {
  228. width: 360px;
  229. z-index: 9;
  230. --arrow-width: 20px;
  231. --bottom-left: 310px;
  232. --back-color: rgba(0, 0, 0, 0.7);
  233. .hot-styles {
  234. margin: 0;
  235. }
  236. }
  237. }
  238. }
  239. </style>