|
@@ -0,0 +1,274 @@
|
|
|
+<template>
|
|
|
+ <div class="hot-styles">
|
|
|
+ <div class="add item" v-if="!props.all">
|
|
|
+ <span class="fun-ctrl">
|
|
|
+ <ui-input
|
|
|
+ class="input"
|
|
|
+ preview
|
|
|
+ accept=".jpg, .jpeg, .png"
|
|
|
+ @update:modelValue="iconUpload"
|
|
|
+ type="file"
|
|
|
+ >
|
|
|
+ <template v-slot:replace>
|
|
|
+ <ui-icon type="add" class="icon" />
|
|
|
+ </template>
|
|
|
+ </ui-input>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-for="hotStyle in styleAll"
|
|
|
+ class="item"
|
|
|
+ :class="{ active: active === hotStyle }"
|
|
|
+ @click="clickHandler(hotStyle)"
|
|
|
+ >
|
|
|
+ <span>
|
|
|
+ <img :src="hotStyle.icon" />
|
|
|
+ <ui-icon
|
|
|
+ v-if="!hotStyle.default"
|
|
|
+ class="delete"
|
|
|
+ type="close"
|
|
|
+ @click.stop="emit('delete', hotStyle)"
|
|
|
+ />
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-if="!props.all && props.styles.length > 5"
|
|
|
+ class="add item style-more"
|
|
|
+ @click="showAll = !showAll"
|
|
|
+ >
|
|
|
+ <span class="fun-ctrl">
|
|
|
+ <ui-icon :type="showAll ? 'pull-up' : 'pull-down'" class="icon" />
|
|
|
+ <ui-bubble
|
|
|
+ class="more-content"
|
|
|
+ :show="showAll"
|
|
|
+ @click.stop
|
|
|
+ type="bottom">
|
|
|
+
|
|
|
+ <styles
|
|
|
+ :styles="styles.filter(style => !styleAll.includes(style))"
|
|
|
+ :active="active"
|
|
|
+ all
|
|
|
+ @quitMore="showAll = false"
|
|
|
+ @uploadStyles="addStyles" @change="clickHandler"
|
|
|
+ @delete="style => emit('delete', style)"
|
|
|
+ />
|
|
|
+ </ui-bubble>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- <div class="buttons" v-if="props.all">
|
|
|
+ <ui-button type="submit" class="button" @click="emit('quitMore')">取消</ui-button>
|
|
|
+ <ui-button type="primary" class="button" @click="enterHandler">保存</ui-button>
|
|
|
+ </div> -->
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { TaggingStyle, TaggingStyles } from '@/store'
|
|
|
+import { TemploraryID } from '@/store'
|
|
|
+import { ref, computed, defineEmits } from 'vue'
|
|
|
+import { Cropper } from 'bill/index'
|
|
|
+
|
|
|
+export type LocalTaggingStyle = Omit<TaggingStyle, 'icon'> & {
|
|
|
+ icon: { file: File; preview: string }
|
|
|
+}
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ styles: TaggingStyles
|
|
|
+ active: TaggingStyle
|
|
|
+ all?: boolean
|
|
|
+}>()
|
|
|
+
|
|
|
+const emit = defineEmits<{
|
|
|
+ (e: 'change', style: TaggingStyle): void
|
|
|
+ (e: 'delete', style: TaggingStyle): void
|
|
|
+ (e: 'uploadStyles', styles: Array<LocalTaggingStyle>): void
|
|
|
+ (e: 'quitMore'): void
|
|
|
+}>()
|
|
|
+
|
|
|
+const showAll = ref(false)
|
|
|
+const styleAll = computed(() => {
|
|
|
+ if (props.all) {
|
|
|
+ return props.styles
|
|
|
+ } else {
|
|
|
+ const styles = props.styles.slice(0, props.styles.length > 5 ? 4 : 5)
|
|
|
+ if (!styles.includes(props.active)) {
|
|
|
+ styles[3] = props.active
|
|
|
+ }
|
|
|
+ return styles
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const iconUpload = async ({ file, preview }: LocalTaggingStyle['icon']) => {
|
|
|
+ const data = await Cropper.open(preview)
|
|
|
+ if (data) {
|
|
|
+ const item = {
|
|
|
+ id: TemploraryID,
|
|
|
+ icon: { file: data[0], preview: data[1] },
|
|
|
+ name: file.name,
|
|
|
+ default: false,
|
|
|
+ }
|
|
|
+ emit('uploadStyles', [item])
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const clickHandler = (hotStyle: TaggingStyle) => {
|
|
|
+ if (!props.all) {
|
|
|
+ showAll.value = false
|
|
|
+ }
|
|
|
+ emit('change', hotStyle)
|
|
|
+}
|
|
|
+
|
|
|
+const addStyles = (newStyles: Array<LocalTaggingStyle>) => {
|
|
|
+ emit('uploadStyles', newStyles)
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.hot-styles {
|
|
|
+ --size: 40px;
|
|
|
+ --icon-size: calc(var(--size) * 0.85);
|
|
|
+ margin: 24px 0;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(var(--size), 1fr));
|
|
|
+ gap: calc(var(--size) / 4);
|
|
|
+ align-items: start;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ .item {
|
|
|
+ --un-active-color: rgba(var(--colors-primary-base-fill), 0);
|
|
|
+ --active-transition: .3s ease;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &.disable {
|
|
|
+ opacity: .3;
|
|
|
+ pointer-events: none;
|
|
|
+ cursor: inherit;
|
|
|
+ }
|
|
|
+
|
|
|
+ span {
|
|
|
+ width: var(--size);
|
|
|
+ height: var(--size);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border: 1px solid var(--un-active-color);
|
|
|
+ position: relative;
|
|
|
+ transition: border var(--active-transition);
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ .input {
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: var(--icon-size);
|
|
|
+ height: var(--icon-size);
|
|
|
+ // outline: 1px dashed var(--un-active-color);
|
|
|
+ transition: outline-color var(--active-transition);
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .delete {
|
|
|
+ --round-size: calc(var(--size) * 0.45);
|
|
|
+ position: absolute;
|
|
|
+ width: var(--round-size);
|
|
|
+ height: var(--round-size);
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: rgba(250, 63, 72, 1);
|
|
|
+ right: calc(var(--round-size) * -1 / 2);
|
|
|
+ top: calc(var(--round-size) * -1 / 2);
|
|
|
+ transition: background-color var(--active-transition);
|
|
|
+ font-size: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #fff;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity .3s ease;
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ transition: color var(--active-transition);
|
|
|
+ margin-top: calc(var(--size) / 4);
|
|
|
+ text-align: center;
|
|
|
+ color: rgb(var(--colors-primary-fill));
|
|
|
+ font-size: var(--small-size);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ color: rgba(var(--colors-primary-base-fill), 1);
|
|
|
+ --un-active-color: rgba(var(--colors-primary-base-fill), 1);
|
|
|
+
|
|
|
+ span img {
|
|
|
+ outline-color: rgb(var(--colors-primary-fill));
|
|
|
+ }
|
|
|
+ p {
|
|
|
+ color: currentColor;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &:not(.style-more):hover {
|
|
|
+ .delete {
|
|
|
+ opacity: 0.5;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .add {
|
|
|
+ height: 100%;
|
|
|
+ align-items: center;
|
|
|
+ display: flex;
|
|
|
+ flex: none;
|
|
|
+
|
|
|
+ span {
|
|
|
+ font-size: calc(var(--icon-size) * 0.4);
|
|
|
+ border: none;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ top: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ width: var(--icon-size);
|
|
|
+ height: var(--icon-size);
|
|
|
+ border-radius: 2px;
|
|
|
+ border: 1px solid var(--colors-border-color);
|
|
|
+ transition: border-color .3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover::before {
|
|
|
+ border-color: rgba(255,255,255,1);
|
|
|
+ }
|
|
|
+ &:active::before {
|
|
|
+ border-color: var(--colors-primary-base) !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .style-more {
|
|
|
+ .fun-ctrl {
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .more-content {
|
|
|
+ width: 360px;
|
|
|
+ z-index: 9;
|
|
|
+ --arrow-width: 20px;
|
|
|
+ --bottom-left: 310px;
|
|
|
+ --back-color: rgba(0, 0, 0, 0.7);
|
|
|
+
|
|
|
+ .hot-styles {
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+</style>
|