- index.vue
<template>
<div>
<el-button type="primary" @click="showDialog">添加邮递配置</el-button>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="800px" :before-close="handleCloseDialog">
<el-form ref="form" :model="form" :rules="formRules" label-width="120px">
<el-form-item label="邮递名称" prop="name">
<el-input v-model="form.name" style="width: 200px;"></el-input>
</el-form-item>
<el-form-item label="邮递角色" prop="role">
<el-select v-model="form.role" placeholder="请选择" class="custom-select">
<el-option label="邮政员" value="postal"></el-option>
<el-option label="管理员" value="admin"></el-option>
</el-select>
</el-form-item>
<el-form-item label="邮政编号ID">
<el-input v-model="form.postListId" placeholder="请输入邮政编号ID" style="width: 200px;"></el-input>
</el-form-item>
<el-form-item label="邮递周期" prop="cycleItem">
<el-select v-model="form.cycleItem" placeholder="邮递周期" class="custom-select"
@change="handleCycleChange">
<el-option label="周期" value="cycle"></el-option>
</el-select>
<el-select v-model="form.period" placeholder="周期选项">
<el-option v-for="periodsItem in periods" :key="periodsItem.value" :label="periodsItem.label"
:value="periodsItem.value"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<template #label>
<span ref="customLabelExpression" />
</template>
<PostCondition v-model="conditions" :item-disabled="itemDisabled" :show-item-btn="showItemBtn" />
</el-form-item>
<el-form-item label="实时邮递次数">
<div class="input-row">
<el-col style="max-width:200px;">
<div class="flexible-input-group">
<el-input v-model="form.frequencyDay" class="flexible-input"
@input="handleFrequencyInput"></el-input>
<span class="input-unit">天</span>
</div>
</el-col>
<el-col style="max-width:200px;">
<div class="flexible-input-group">
<el-input v-model="form.frequencyCount" class="flexible-input"
@input="handleTimesInput"></el-input>
<span class="input-unit">次</span>
</div>
</el-col>
</div>
</el-form-item>
<el-form-item label="邮递方式" prop="selectedMethods">
<el-select v-model="form.selectedMethods" multiple placeholder="请选择" class="custom-select">
<el-option label="邮件" value="0"></el-option>
<el-option label="短信" value="1"></el-option>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" placeholder="请选择" class="custom-select">
<el-option label="上线" value="1"></el-option>
<el-option label="下线" value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="推送内容" prop="content">
<el-input type="textarea" v-model="form.content" :rows="5" maxlength="400" show-word-limit
style="width: 600px;"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import '@/styles/textarea.css'
import PostCondition from './PostCondition.vue'
export default {
components: {
PostCondition,
},
data() {
return {
dialogVisible: false,
dialogTitle: '添加邮递配置',
form: {
name: '中国邮政', // 邮递名称
role: 'postal', // 邮递角色
period: '', // 邮递周期单位
postListId: '', // 邮政编号ID
cycleItem: '', // 邮递周期选项
frequencyDay: '', // 频率:天
frequencyCount: '', // 频率:次
selectedMethods: [], // 邮递方式
status: '1',
content: '',
richTextContent: '', // 富文本内容
expression: ''// 条件表达式
},
selectConditions: [], // 邮递条件选项列表,根据周期动态设置
bakSelectConditions: [], // 邮递条件选项列表备份,用于重置
periods: [], // 邮递周期单位列表
disableConditionSelect: false, // 控制邮递条件选择框是否禁用
showPercentageSuffix: false, // 是否显示百分比输入框后缀
formRules: { // 表单验证规则
name: [
{ required: true, message: '请输入邮递名称', trigger: 'blur' }
],
role: [
{ required: true, message: '请选择邮递角色', trigger: 'change' }
],
cycleItem: [
{ required: true, message: '请选择邮递周期', trigger: 'change' }
],
period: [
{ required: true, message: '请选择邮递周期单位', trigger: 'change' }
],
method: [
{ required: true, message: '请选择邮递方式', trigger: 'change' }
],
status: [
{ required: true, message: '请选择状态', trigger: 'change' }
],
content: [
{ required: true, message: '请输入推送内容', trigger: 'blur' }
],
selectedMethods: [
{ required: true, message: '请选择通知方式', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value.length === 0) {
callback(new Error('请选择邮递方式'));
} else {
callback();
}
},
trigger: 'change'
}
],
richTextContent: [
{ required: true, message: '请输入内容', trigger: 'blur' }
],
showPercentageSuffix: false
},
periodsToValue: {
'day': 1,
'week': 2,
'month': 3
},
conditions: [{ field: '', operator: '', value: '' }], // 邮递条件
itemDisabled: false,
showItemBtn: true
};
},
methods: {
showDialog() {
this.dialogVisible = true;
this.$nextTick(() => {
this.updateCustomLabel();
});
},
handleCloseDialog(done) {
// 清空表单数据
this.$refs.form.resetFields();
// 关闭对话框
done();
},
handleConditionsData() {
const result = this.conditions.map(condition => {
let cleanedValue = condition.value.replace(/,/g, '')
if (!isNaN(cleanedValue) && cleanedValue.includes('.')) {
cleanedValue = (parseFloat(cleanedValue) / 100).toFixed(4)
}
return `${condition.field}|${condition.operator}|${cleanedValue}`
}).join('&')
return result
},
handleSubmit() {
// 手动触发一次表单验证
this.$refs.form.validate((valid) => {
if (valid) {
// 表单验证通过,执行提交操作
const allValid = this.conditions.every(item => item.field && item.operator && item.value)
if (!allValid) {
this.$message.error('邮递条件选项还有未填完,请仔细检查一下!')
return
}
// 根据选中的通知方式处理内容并构造数据格式
this.form.expression = this.handleConditionsData()
this.printVal()
alert('提交成功!');
} else {
alert('表单验证失败!');
}
});
},
updateCustomLabel() {
const label = '* 邮递条件';
const firstChar = label.charAt(0);
const restOfLabel = label.slice(1);
// 创建一个 span 元素
const spanElement = document.createElement('span');
// 设置第一个字符的颜色为 #F56C6C
spanElement.innerHTML = `<span style="color: #F56C6C">${firstChar}</span>${restOfLabel}`;
// 替换 el-form-item 的 label 内容
this.$refs.customLabelExpression.innerHTML = '';
this.$refs.customLabelExpression.appendChild(spanElement);
},
handleCycleChange(value) {
this.disableConditionSelect = false;
// 根据周期设置条件选项列表
if (value === 'cycle') {
this.periods = [
{ label: '天', value: 'day' },
{ label: '周', value: 'week' },
{ label: '月', value: 'month' },
];
}
},
checkFloatNumber(value) {
let number = value
.replace(/[^\d.]/g, '') // 清除非数字和小数点字符
.replace(/\.{2,}/g, '.') // 只保留一个小数点
.replace(/^(-)*(\d+)\.(\d{0,2}).*$/, '$1$2.$3'); // 限制小数点后两位
return number;
},
checkIntNumber(value) {
let number = value
.replace(/[^\d.]/g, '') // 清除非数字和小数点字符
.replace(/\.{1,}/g, '') // 保留0个小数点
return number;
},
formatNumber(value) {
console.log("去除输入中的非数字字符")
// 去除输入中的非数字字符
if (!value) return '';
let number = value.toString().replace(/\D/g, '');
// 格式化为千位分隔符的形式
let formatted = new Intl.NumberFormat('en-US').format(number);
return formatted;
},
handleSelectedMethods(listData) {
// 将数组转换成逗号分隔的字符串
const result = listData.join(',');
return result;
},
handleTouchCount(valueKey) {
let value = this.form[valueKey].trim(); // 获取输入框的值
if (value === '') {
return; // 检查是否为空
}
let intValue = parseInt(value, 10); // 尝试转换为整数
if (isNaN(intValue) || !Number.isInteger(intValue) || intValue < 1) {
// 如果不是合法的整数,则给出警告并设置为最小匹配值
this.$message.warning({
message: '已自动将其自动设置为最小匹配值',
duration: 1000
});
this.form[valueKey] = 1; // 设置为最小匹配值
} else {
// 更新数据
this.form[valueKey] = intValue.toString();
}
},
handleFrequencyInput() {
this.handleTouchCount('frequencyDay');
},
handleTimesInput() {
this.handleTouchCount('frequencyCount');
},
printVal() {
console.log("邮递名称:", this.form.name); // 邮递名称
console.log("邮递角色:", this.form.role); // 邮递角色
console.log("邮政编号ID:", this.form.postListId); // 邮政编号ID
console.log("邮递周期:", this.periodsToValue[this.form.period]); // 邮递周期
console.log("邮递条件表达式:", this.form.expression);// 邮递条件表达式
console.log("邮递频率:", this.form.frequencyDay); // 邮递频率
console.log("邮递次数:", this.form.frequencyCount); // 邮递次数
console.log("邮递方式:", this.handleSelectedMethods(this.form.selectedMethods));// 邮递方式
console.log("状态:", this.form.status);// 状态
console.log("推送内容:", this.form.content);// 推送内容
},
},
};
</script>
<style scoped>
.dialog-footer {
text-align: center;
}
.input-row {
display: flex;
align-items: center;
}
.flexible-input-group {
/* display: flex; */
/* align-items: center; */
margin-right: 20px;
/* 调整输入框组之间的间距 */
}
.flexible-input-group .flexible-input {
width: 150px !important;
margin-right: 10px;
}
/* .input-row /deep/ .flexible-input {
width: 100px;
} */
.input-unit {
margin-left: 5px;
/* 调整单位文本和输入框之间的间距 */
}
.custom-select {
width: 200px;
margin-right: 10px;
}
</style>
PostCondition.vue
<template>
<div>
<!-- 使用渲染函数自定义 label 的内容 -->
<div v-for="(condition, index) in conditions" :key="generateKey(index)" class="condition-item">
<el-row :class="{ 'error-row': hasError(index) }" class="condition-row" style="margin-top: 2.5px">
<div class="condition-content">
<el-col>
<!-- 邮递条件字段选择 -->
<el-form-item>
<el-select v-model="condition.field" placeholder="邮递条件" class="custom-select"
:disabled="itemDisabled">
<el-option v-for="(selCondition) in selectConditions" :key="selCondition.label"
:label="selCondition.label" :value="selCondition.value" />
</el-select>
</el-form-item>
</el-col>
<el-col>
<!-- 运算符选择-->
<el-form-item style="width:100px">
<el-select v-model="condition.operator" placeholder="运算符" class="custom-select"
:disabled="itemDisabled" style="width:100px">
<el-option label=">" value=">" />
<el-option v-if="!(condition.field === 'email_delivery_success_rate' || condition.field === 'email_delivery_failed_rate')"
label="=" value="=" />
<el-option label="<" value="<" />
</el-select>
</el-form-item>
</el-col>
<el-col class="input-col">
<!-- 值输入 -->
<el-form-item>
<el-input v-model="condition.value" class="flexible-input" placeholder="有效值"
:disabled="itemDisabled" style="min-width:200px" @blur="updateAndUnFormat(index)"
@input="checkConditionValueInput(index)" @focus="unFormatNumber(index)">
<i v-if="condition.field === 'email_delivery_success_rate' || condition.field === 'email_delivery_failed_rate'"
slot="suffix" style="font-style: normal; margin-right: 10px;">%</i>
</el-input>
</el-form-item>
</el-col>
<el-col class="button-col">
<!-- 添加和移除按钮 -->
<div class="button-container">
<el-button v-if="showItemBtn" icon="el-icon-plus" class="custom-button round-icon"
type="primary" :disabled="itemDisabled" @click="addCondition(index)" />
<el-button v-if="showItemBtn" icon="el-icon-close" class="custom-button round-icon"
type="danger" :disabled="itemDisabled" @click="removeCondition(index)" />
</div>
</el-col>
</div>
</el-row>
</div>
</div>
</template>
<script>
import { v4 as uuidv4 } from 'uuid'
export default {
props: {
value: {
type: Array,
required: true
},
itemDisabled: {
type: Boolean,
default: true
},
showItemBtn: {
type: Boolean,
default: false
}
},
data() {
return {
selectConditions: [
{ label: '邮件人数', value: 'recipient_user', showPercentageSuffix: false },
{ label: '新增邮件人数', value: 'new_recipient_user', showPercentageSuffix: false },
{ label: '流水金额,分', value: 'pay_amount', showPercentageSuffix: false },
{ label: '邮件次数', value: 'recipient_count', showPercentageSuffix: false },
{ label: '传输时长,秒', value: 'transfer_duration', showPercentageSuffix: false },
{ label: '滞留时长,秒', value: 'dwell_duration', showPercentageSuffix: false },
{ label: '运转次数', value: 'operating_cycles', showPercentageSuffix: false },
{ label: '邮件传送成功抵达率', value: 'email_delivery_success_rate', showPercentageSuffix: true },
{ label: '邮件传送失败成交率', value: 'email_delivery_failed_rate', showPercentageSuffix: true }
],
addBool: true
}
},
computed: {
conditions: {
get() {
return this.value
},
set(newValue) {
this.$emit('input', newValue)
}
}
},
methods: {
hasError(index) {
const condition = this.conditions[index]
return !condition.field || !condition.operator || !condition.value
},
updateAndUnFormat(index) {
this.processConditionValue(index)
const oneCondition = this.conditions[index]
if (oneCondition.field !== 'email_delivery_success_rate' && oneCondition.field !== 'email_delivery_failed_rate') {
oneCondition.value = this.formatNumber(oneCondition.value)
}
},
checkConditionValueInput(index) {
const oneCondition = this.conditions[index]
const value = oneCondition.value
if (oneCondition.field === 'email_delivery_success_rate' || oneCondition.field === 'email_delivery_failed_rate') {
oneCondition.value = this.checkFloatNumber(value) // 调用 checkIntNumber 处理输入值
} else {
oneCondition.value = this.checkIntNumber(value) // 调用 checkIntNumber 处理输入值
}
},
checkFloatNumber(value) {
const number = value
.replace(/[^\d.]/g, '') // 清除非数字和小数点字符
.replace(/\.{2,}/g, '.') // 只保留一个小数点
.replace(/^(-)*(\d+)\.(\d{0,2}).*$/, '$1$2.$3') // 限制小数点后两位
return number
},
checkIntNumber(value) {
const number = value
.replace(/[^\d.]/g, '') // 清除非数字和小数点字符
.replace(/\.{1,}/g, '') // 保留0个小数点
return number
},
formatNumber(value) {
// 去除输入中的非数字字符
if (!value) return ''
const number = value.toString().replace(/\D/g, '')
// 格式化为千位分隔符的形式
const formatted = new Intl.NumberFormat('en-US').format(number)
return formatted
},
// 去除千位分隔符并返回数字
unFormatNumber(index) {
this.$nextTick(() => {
const oneCondition = this.conditions[index]
if (oneCondition.field !== 'email_delivery_success_rate' && oneCondition.field !== 'email_delivery_failed_rate') {
// 去除输入中的非数字字符,包括逗号
oneCondition.value = oneCondition.value.toString().replace(/[^\d]/g, '')
}
})
},
addCondition() {
// 创建一个新的条件对象
const newCondition = { field: '', operator: '', value: '' }
// 将新条件插入到数组末尾
this.conditions.push(newCondition)
},
removeCondition(index) {
// 如果只有一个条件,则仅清空输入框的值,不删除条件对象
if (this.conditions.length > 1) {
this.conditions.splice(index, 1)
} else {
this.conditions[index].value = ''
}
},
processConditionValue(index) {
const oneCondition = this.conditions[index]
const value = oneCondition.value
console.log('processConditionValue value:', value)
if (!value) {
return // 如果数字为空,不继续进行后续操作
}
if (oneCondition.field === 'email_delivery_success_rate' || oneCondition.field === 'email_delivery_failed_rate') {
this.handleFloatNumberInput(value, index)
} else {
this.handleIntNumberInput(value, index)
}
},
handleIntNumberInput(value, index) {
// 去除前导0
value = value.toString().replace(/^0+(?!$)/, '')
// 纠正输入的整数
if (value === '' || isNaN(value)) {
this.conditions[index].value = ''
} else {
this.conditions[index].value = value
}
},
handleFloatNumberInput(value, index) {
const number = parseFloat(value).toFixed(2) // 将数字转换为浮点数再转换回字符串,去掉前导零
if (number < 0 || number > 100) {
this.$message.error({
message: '输入的范围应为0-100%',
duration: 1000
})
this.conditions[index].value = undefined
return
}
// 判断价格小数部分是否需要补全
const needsCompletion = !/\.\d{2}$/.test(value)
this.conditions[index].value = number
// 如果需要补全,则提示用户
if (needsCompletion) {
this.$message.info({
message: '数字已自动补全为两位小数。',
duration: 1000
})
}
},
generateKey(index) {
return `${this.conditions[index].field}-${index}`
},
getUUid(index) {
return `${uuidv4()}-${this.selectConditions[index]}`
},
},
}
</script>
<style scoped>
.condition-item {
/* margin-bottom: 10px; */
margin-bottom: 18px;
}
.condition-row {
display: flex;
align-items: center;
}
.condition-content {
display: flex;
width: 100%;
}
.custom-select {
width: 200px;
margin-right: 10px;
}
.flexible-input {
flex: 1;
padding-left: 5px;
margin-right: 10px;
}
.round-icon {
width: 32px;
height: 32px;
border-radius: 50%;
font-size: 16px;
line-height: 32px;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
}
.round-icon.el-button--primary {
background-color: #38aa7e;
color: #ffffff;
}
.round-icon.el-button--danger {
background-color: #F56C6C;
color: #ffffff;
}
.error-row {
color: #e45b5b;
margin-top: 5px;
}
.error-text {
margin-top: 5px;
color: #e45b5b;;
height: 16px;
line-height: 16px;
}
.button-container {
display: flex;
}
.input-col {
/* 添加输入框和按钮之间的间距 */
margin-right: 10px;
}
</style>
文章评论