vue component model prop event All In One
vue component model prop event All In One
vue components & custom events
https://vuejs.org/v2/guide/components-custom-events.html
WTF
demo
<template>
<div class="inline-block">
<el-select
:class="['avatar-select', selected.url ? 'selected' : 'not-selected']"
v-model="selected.id"
popper-class="avatar-select-dropdown"
:loading="loading"
:popper-append-to-body="false"
@change="handleChangeAvatar">
<img class="result-img m-r-8" slot="prefix" :src="selected.url" />
<div v-show="avatarList.length <= 1" class="no-data c-999 f12">
暂无数据
</div>
<div v-show="avatarList.length > 1" class="avatar-select-options-content" style="max-height: 214px; overflow: auto;">
<el-option
v-show="item.id"
v-for="item in avatarList" :key="item.id"
:value="item.id" :label="item.name">
<div class="avatar-select-item">
<div class="avatar-select-item-img">
<img :src="item.url">
<span class="name c-333 f12 m-l-10 text-ellipsis" :title="item.name">{{item.name}}</span>
</div>
<a class="delete f12" v-show="showDeleteBtn" @click.stop="handleDeleteAvatar(item)">删除</a>
</div>
</el-option>
</div>
<div v-show="showAvatarBottom" class="avatar-select-btn">
<a class="hand" v-show="showAddBtn" @click.stop="handleAddAvatar">新建头像及昵称跳转页</a>
<span v-show="showAddBtn" class="m-l-8 m-r-8" style="color: #c0c4cc;">|</span>
<a class="hand" v-show="showRefreshBtn" @click.stop="getAvatarList">刷新</a>
</div>
</el-select>
<AddAvatarModal
:visible="addAvatarModalvisible"
:spreaderId="spreaderId"
:gameId="gameId"
@close="handleAddAvatarModalClose"
@confirm="handleAddAvatarModalconfirm">
</AddAvatarModal>
<el-dialog
title="删除头像及昵称跳转页"
:visible.sync="deleteModalvisible"
width="420px"
:modal-append-to-body='false'
:before-close="handleDeleteModalClose">
<el-row class="p-l-20 p-r-20">
<el-col :span="24">
<i class="el-icon-warning m-r-10 f18 c-yellow"></i>
<span class="vertical-top">{{ `确定将删除“${currentDelete.name}”?` }}</span>
</el-col>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button @click="handleDeleteModalClose">取 消</el-button>
<el-button type="primary" :loading="confirmDeleteBtnLoading" @click="handleDeleteConfirm">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import AddAvatarModal from './AddAvatarModal';
import Advertise2GdtService from '@/services/advertise-2/gdtService';
import Utils from '@/utils';
const allOperations = ['add','refresh','delete'];
let avatarListMap = {};
const defaultOption = { id: 0, name: '请选择', url: '' };
export default {
name: 'AvatarSelect',
components: { AddAvatarModal },
props: {
value: {
type: [Number, String],
default: 0,
},
operations: {
type: Array,
default: () => allOperations,
},
spreaderId: {
type: Number,
default: 0,
},
gameId: {
type: Number,
default: 0,
},
},
model: {
prop: 'value',
event: 'change'
},
data () {
return {
avatarList: [],
selected: {
id: '',
url: '',
name: '',
},
addAvatarModalvisible: false,
deleteModalvisible: false,
currentDelete: {
id: '',
url: '',
name: '',
},
confirmDeleteBtnLoading: false,
loading: false,
};
},
computed: {
showAddBtn () {
return this.operations.includes('add');
},
showRefreshBtn () {
return this.operations.includes('refresh');
},
showDeleteBtn () {
return this.operations.includes('delete');
},
showAvatarBottom () {
return this.showAddBtn || showRefreshBtn;
},
},
mounted () {
this.getAvatarList();
},
methods: {
getAvatarList () {
const param = {
game_id: this.gameId,
spreader_id: this.spreaderId,
};
this.loading = true;
return Advertise2GdtService.getGdtProfileList(param).then(resp => {
let avatarList = resp.data.data.rows || [];
this.loading = false;
avatarList = Utils.convertObjInArray(avatarList, { 'head_image_url': 'url', id: 'id', name: 'name', description: 'description' });
this.avatarList = [].concat(defaultOption, avatarList);
avatarListMap = {...avatarListMap, ...Utils.getListMap(this.avatarList, 'id')};
this.$nextTick(() => {
const listStyle = document.querySelector('.avatar-select .el-select-dropdown__list')?.style;
if (listStyle) {
listStyle.paddingTop = 0;
listStyle.paddingBottom = 0;
}
if (this.value) {
this.handleChangeAvatar(this.value);
}
});
}).catch(err => {
this.loading = false;
});
},
handleDeleteModalClose () {
this.deleteModalvisible = false;
},
handleDeleteAvatar (avatarItem) {
this.deleteModalvisible = true;
this.currentDelete = avatarItem;
},
handleDeleteConfirm () {
const param = {
game_id: this.gameId,
spreader_id: this.spreaderId,
id: this.currentDelete.id
};
this.confirmDeleteBtnLoading = true;
return Advertise2GdtService.deleteGdtProfile(param).then(resp => {
this.confirmDeleteBtnLoading = false;
this.$message.success('删除头像成功');
this.handleDeleteModalClose();
setTimeout(() => {
this.getAvatarList();
}, 300);
}).catch(err => {
this.confirmDeleteBtnLoading = false;
});
},
handleAddAvatar () {
this.addAvatarModalvisible = true;
},
handleAddAvatarModalClose () {
this.addAvatarModalvisible = false;
},
handleAddAvatarModalconfirm () {
this.handleAddAvatarModalClose();
// 不加定时器会有闪屏
setTimeout(() => {
this.getAvatarList();
}, 300);
},
handleChangeAvatar (id) {
const option = avatarListMap[id] || {};
if (option.id) {
this.selected.id = option.id;
this.selected.name = option.name;
this.selected.url = option.url;
}
this.$emit('change', id);
},
},
watch: {
value: {
handler (val) {
if (val) {
console.log('va ???', val);
this.handleChangeAvatar(val);
}
},
immediate: true,
},
},
};
</script>
<style lang="scss" scoped>
.avatar-select {
width: 300px;
&.selected {
/deep/ .el-input__inner {
padding-left: 38px;
}
}
&.not-selected {
/deep/ .el-input__inner {
padding-left: 15px;
}
}
/deep/ .el-input__inner::placeholder {
color: #c0c4cc !important;
}
.result-img {
width: 24px;
height: 24px;
border-radius: 2px;
position: relative;
top: -1px;
}
}
.el-select-dropdown.avatar-select-dropdown {
.el-select-dropdown__wrap {
.el-select-dropdown__item {
padding: 0;
height: 56px;
line-height: 56px;
.avatar-select-item {
box-sizing: content-box;
display: flex;
justify-content: space-between;
align-items: center;
height: 56px;
// line-height: 56px;
padding: 0 10px;
.avatar-select-item-img {
display: flex;
justify-content: flex-start;
align-items: center;
width: calc(100% - 50px);
}
img {
width: 40px;
height: 40px;
border-radius: 8px;
}
.name {
display: inline-block;
width: 178px;
}
.delete {
justify-content: flex-end;
}
}
}
}
.avatar-select-btn {
box-sizing: border-box;
height: 36px;
line-height: 36px;
padding: 0 10px;
border-top: 1px solid #EBEEF5;
&.no-top-border {
border-top-color: transparent;
}
}
.no-data {
height: 80px;
line-height: 80px;
text-align: center;
}
}
</style>
refs
©xgqfrms 2012-2020
www.cnblogs.com/xgqfrms 发布文章使用:只允许注册用户才可以访问!
原创文章,版权所有©️xgqfrms, 禁止转载 🈲️,侵权必究⚠️!
本文首发于博客园,作者:xgqfrms,原文链接:https://www.cnblogs.com/xgqfrms/p/15350050.html
未经授权禁止转载,违者必究!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
2020-09-28 为什么 Koa 的官方文档那么丑呀?
2020-09-28 如何获取豆瓣电影 API Key All In One
2020-09-28 free open movie API all in one
2020-09-28 free open music API all in one
2020-09-28 js console.log color all in one
2020-09-28 支付宝小程序-生命周期图解
2020-09-28 微信小程序-生命周期图解