179 行
3.6 KiB
Plaintext
179 行
3.6 KiB
Plaintext
<template>
|
||
<view class="uploader-container">
|
||
<view class="uploader-grid">
|
||
<!-- 已选图片列表 -->
|
||
<view
|
||
v-for="(url, index) in state.images"
|
||
:key="index"
|
||
class="uploader-item"
|
||
>
|
||
<image
|
||
class="uploader-img"
|
||
:src="url"
|
||
mode="aspectFill"
|
||
@click="handlePreview(index)"
|
||
/>
|
||
<view
|
||
v-if="!disabled"
|
||
class="uploader-delete-btn"
|
||
@click="handleDelete(index)"
|
||
>
|
||
<text class="uploader-delete-icon">×</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 上传卡片触发区 -->
|
||
<view
|
||
v-if="canAdd && !disabled"
|
||
class="uploader-trigger"
|
||
@click="handleChoose"
|
||
>
|
||
<text class="uploader-trigger-plus">+</text>
|
||
<text class="uploader-trigger-text">{{ state.uploading ? '上传中' : '添加图片' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import {
|
||
createImageUploaderState,
|
||
removeImageByIndex,
|
||
previewImageAt,
|
||
chooseAndUploadImages,
|
||
ImageUploaderState,
|
||
} from './useImageUploader.uts';
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
modelValue?: string[];
|
||
multiple?: boolean;
|
||
maxCount?: number;
|
||
disabled?: boolean;
|
||
}>(),
|
||
{
|
||
modelValue: () => [] as string[],
|
||
multiple: false,
|
||
maxCount: 9,
|
||
disabled: false,
|
||
}
|
||
);
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'update:modelValue', value: string[]): void;
|
||
(e: 'change', value: string[]): void;
|
||
}>();
|
||
|
||
const state = reactive(createImageUploaderState(props.modelValue));
|
||
|
||
watch(
|
||
() => props.modelValue,
|
||
(newVal: string[]) => {
|
||
state.images = newVal != null ? newVal.slice() : [];
|
||
}
|
||
);
|
||
|
||
const canAdd = computed(() => {
|
||
const limit = props.multiple ? props.maxCount : 1;
|
||
return state.images.length < limit;
|
||
});
|
||
|
||
function handlePreview(index: number): void {
|
||
previewImageAt(state, index);
|
||
}
|
||
|
||
function handleDelete(index: number): void {
|
||
removeImageByIndex(state, index);
|
||
emit('update:modelValue', state.images);
|
||
emit('change', state.images);
|
||
}
|
||
|
||
async function handleChoose(): Promise<void> {
|
||
if (state.uploading || props.disabled) {
|
||
return;
|
||
}
|
||
const ok = await chooseAndUploadImages(state, {
|
||
multiple: props.multiple,
|
||
maxCount: props.multiple ? props.maxCount : 1,
|
||
});
|
||
if (ok) {
|
||
emit('update:modelValue', state.images);
|
||
emit('change', state.images);
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.uploader-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 100%;
|
||
}
|
||
|
||
.uploader-grid {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
|
||
.uploader-item {
|
||
position: relative;
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--oc-border, #E3E7ED);
|
||
background-color: var(--oc-surface, #F7F8FA);
|
||
}
|
||
|
||
.uploader-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.uploader-delete-btn {
|
||
position: absolute;
|
||
top: 4px;
|
||
right: 4px;
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 10px;
|
||
background-color: rgba(0, 0, 0, 0.6);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.uploader-delete-icon {
|
||
color: #FFFFFF;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
}
|
||
|
||
.uploader-trigger {
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 12px;
|
||
border: 1px dashed var(--oc-border, #C9CDD4);
|
||
background-color: var(--oc-surface, #F7F8FA);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.uploader-trigger-plus {
|
||
font-size: 24px;
|
||
color: var(--oc-text-2, #667085);
|
||
line-height: 1;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.uploader-trigger-text {
|
||
font-size: 11px;
|
||
color: var(--oc-text-2, #667085);
|
||
}
|
||
</style>
|