feat: select rocket prop rewards from drawer
This commit is contained in:
parent
eb25cb8981
commit
b5a86072f2
@ -73,6 +73,10 @@ export default {
|
||||
type: String,
|
||||
return: true,
|
||||
default: ''
|
||||
},
|
||||
propsTypes: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@ -92,6 +96,7 @@ export default {
|
||||
{ propsType: 'EMOJI', nameKey: 'pages.propsSourceGroupForm.resourceType.emoji', loading: false, loadData: false, list: [], placeholderKey: 'pages.propsSourceSelectDrawer.unit.quantity' },
|
||||
{ propsType: 'CHAT_BUBBLE', nameKey: 'pages.propsSourceGroupForm.resourceType.chatBubble', loading: false, loadData: false, list: [], placeholderKey: 'pages.propsSourceSelectDrawer.unit.days' },
|
||||
{ propsType: 'FLOAT_PICTURE', nameKey: 'pages.propsSourceGroupForm.resourceType.floatPicture', loading: false, loadData: false, list: [], placeholderKey: 'pages.propsSourceSelectDrawer.unit.days' },
|
||||
{ propsType: 'DATA_CARD', nameKey: 'pages.propsSourceGroupForm.resourceType.dataCard', loading: false, loadData: false, list: [], placeholderKey: 'pages.propsSourceSelectDrawer.unit.days' },
|
||||
{ propsType: 'CUSTOMIZE', nameKey: 'pages.propsSourceGroupForm.resourceType.customizeReadonly', loading: false, loadData: false, list: [], placeholderKey: 'pages.propsSourceSelectDrawer.unit.description' },
|
||||
{ propsType: 'FRAGMENTS', nameKey: 'pages.propsSourceGroupForm.resourceType.fragments', loading: false, loadData: false, list: [], placeholderKey: 'pages.propsSourceSelectDrawer.unit.quantity' }
|
||||
],
|
||||
@ -135,8 +140,17 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 业务页面可限制抽屉里的资源类型,避免选到当前发奖链路不支持的资源。
|
||||
if (this.propsTypes.length > 0) {
|
||||
this.configPropsTypes = this.configPropsTypes.filter(item => this.propsTypes.includes(item.propsType))
|
||||
if (!this.configPropsTypes.some(item => item.propsType === this.listQuery.propsType)) {
|
||||
this.listQuery.propsType = this.configPropsTypes[0] ? this.configPropsTypes[0].propsType : ''
|
||||
}
|
||||
}
|
||||
this.configPropsTypesMap = this.configPropsTypesToMap()
|
||||
this.propsTypeChange()
|
||||
if (this.listQuery.propsType) {
|
||||
this.propsTypeChange()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
configPropsTypesToMap() {
|
||||
@ -151,6 +165,9 @@ export default {
|
||||
},
|
||||
loadSource(type) {
|
||||
const that = this
|
||||
if (!that.configPropsTypesMap[type]) {
|
||||
return
|
||||
}
|
||||
if (that.configPropsTypesMap[type].loadData === true) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -8028,6 +8028,11 @@ export default {
|
||||
rewardType: 'Type',
|
||||
rewardName: 'Name',
|
||||
quantity: 'Quantity/Days',
|
||||
days: 'Days',
|
||||
selectedProp: 'Selected Prop',
|
||||
selectProp: 'Select Prop',
|
||||
changeProp: 'Change',
|
||||
selected: 'Selected',
|
||||
detailType: 'Prop Type',
|
||||
detailTypePlaceholder: 'Enter a prop type recognized by the server',
|
||||
cover: 'Cover URL',
|
||||
|
||||
@ -8025,6 +8025,11 @@ export default {
|
||||
rewardType: '类型',
|
||||
rewardName: '名称',
|
||||
quantity: '数量/天数',
|
||||
days: '天数',
|
||||
selectedProp: '已选道具',
|
||||
selectProp: '选择道具',
|
||||
changeProp: '更换',
|
||||
selected: '已选择',
|
||||
detailType: '道具类型',
|
||||
detailTypePlaceholder: '请输入服务端识别的道具类型',
|
||||
cover: '封面地址',
|
||||
|
||||
@ -111,7 +111,7 @@
|
||||
{{ $t('pages.rocketConfig.removeReward') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-row :gutter="12">
|
||||
<el-row :gutter="12" type="flex" align="middle">
|
||||
<el-col :span="6">
|
||||
<el-form-item
|
||||
class="inner-form-item"
|
||||
@ -130,10 +130,10 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col v-if="reward.type === 'GOLD'" :span="18">
|
||||
<el-form-item
|
||||
class="inner-form-item"
|
||||
:label="contentLabel(reward.type)"
|
||||
:label="$t('pages.rocketConfig.goldAmount')"
|
||||
:prop="'rewardConfig.' + index + '.content'"
|
||||
:rules="rewardRules.content"
|
||||
label-width="80px"
|
||||
@ -141,21 +141,34 @@
|
||||
<el-input v-model.trim="reward.content" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col v-else :span="12">
|
||||
<el-form-item
|
||||
class="inner-form-item"
|
||||
:label="$t('pages.rocketConfig.rewardName')"
|
||||
:prop="'rewardConfig.' + index + '.name'"
|
||||
:rules="rewardRules.name"
|
||||
:label="$t('pages.rocketConfig.selectedProp')"
|
||||
:prop="'rewardConfig.' + index + '.content'"
|
||||
:rules="rewardRules.content"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-input v-model.trim="reward.name" />
|
||||
<div v-if="reward.content" class="selected-prop">
|
||||
<el-image class="selected-prop__cover" :src="reward.cover" fit="cover">
|
||||
<div slot="error" class="selected-prop__empty-cover">
|
||||
<i class="el-icon-picture-outline" />
|
||||
</div>
|
||||
</el-image>
|
||||
<span class="selected-prop__name">{{ reward.name || $t('pages.rocketConfig.selected') }}</span>
|
||||
<el-button type="text" @click="openPropsDrawer(index)">
|
||||
{{ $t('pages.rocketConfig.changeProp') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button v-else type="primary" plain icon="el-icon-plus" @click="openPropsDrawer(index)">
|
||||
{{ $t('pages.rocketConfig.selectProp') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="reward.type !== 'GOLD'" :span="6">
|
||||
<el-form-item
|
||||
class="inner-form-item"
|
||||
:label="$t('pages.rocketConfig.quantity')"
|
||||
:label="$t('pages.rocketConfig.days')"
|
||||
:prop="'rewardConfig.' + index + '.quantity'"
|
||||
:rules="rewardRules.quantity"
|
||||
label-width="80px"
|
||||
@ -164,24 +177,6 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col v-if="reward.type === 'PROPS'" :span="12">
|
||||
<el-form-item
|
||||
class="inner-form-item"
|
||||
:label="$t('pages.rocketConfig.detailType')"
|
||||
:prop="'rewardConfig.' + index + '.detailType'"
|
||||
:rules="rewardRules.detailType"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-input v-model.trim="reward.detailType" :placeholder="$t('pages.rocketConfig.detailTypePlaceholder')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="reward.type === 'PROPS' ? 12 : 24">
|
||||
<el-form-item class="inner-form-item" :label="$t('pages.rocketConfig.cover')" label-width="80px">
|
||||
<el-input v-model.trim="reward.cover" :placeholder="$t('pages.rocketConfig.coverPlaceholder')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<el-empty
|
||||
@ -199,19 +194,31 @@
|
||||
</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<props-source-select-drawer
|
||||
v-if="propsDrawerVisible"
|
||||
:sys-origin="'ATYOU'"
|
||||
:props-type="drawerPropsType"
|
||||
:props-types="drawerPropsTypes"
|
||||
@close="closePropsDrawer"
|
||||
@select="selectPropsSource"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listRocketConfigs, updateRocketConfig } from '@/api/rocket-config'
|
||||
import PropsSourceSelectDrawer from '@/components/data/PropsSourceSelectDrawer'
|
||||
|
||||
const createEmptyReward = () => ({
|
||||
id: null,
|
||||
type: 'GOLD',
|
||||
content: '',
|
||||
name: '',
|
||||
name: '金币',
|
||||
quantity: 1,
|
||||
detailType: '',
|
||||
cover: ''
|
||||
cover: '',
|
||||
sourceUrl: ''
|
||||
})
|
||||
|
||||
const createEmptyForm = () => ({
|
||||
@ -224,6 +231,7 @@ const createEmptyForm = () => ({
|
||||
|
||||
export default {
|
||||
name: 'RocketConfig',
|
||||
components: { PropsSourceSelectDrawer },
|
||||
data() {
|
||||
const validatePositiveInteger = (rule, value, callback) => {
|
||||
if (!/^\d+$/.test(String(value || '')) || Number(value) <= 0) {
|
||||
@ -237,6 +245,10 @@ export default {
|
||||
listLoading: false,
|
||||
submitLoading: false,
|
||||
dialogVisible: false,
|
||||
propsDrawerVisible: false,
|
||||
selectingRewardIndex: -1,
|
||||
drawerPropsType: 'AVATAR_FRAME',
|
||||
drawerPropsTypes: [],
|
||||
configList: [],
|
||||
form: createEmptyForm(),
|
||||
rules: {
|
||||
@ -250,10 +262,8 @@ export default {
|
||||
},
|
||||
rewardRules: {
|
||||
type: [{ required: true, message: this.$t('pages.rocketConfig.rewardTypeRequired'), trigger: 'change' }],
|
||||
content: [{ required: true, message: this.$t('pages.rocketConfig.rewardContentRequired'), trigger: 'blur' }],
|
||||
name: [{ required: true, message: this.$t('pages.rocketConfig.rewardNameRequired'), trigger: 'blur' }],
|
||||
quantity: [{ validator: validatePositiveInteger, trigger: 'blur' }],
|
||||
detailType: [{ required: true, message: this.$t('pages.rocketConfig.detailTypeRequired'), trigger: 'blur' }]
|
||||
content: [{ required: true, message: this.$t('pages.rocketConfig.rewardContentRequired'), trigger: 'change' }],
|
||||
quantity: [{ validator: validatePositiveInteger, trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -288,11 +298,6 @@ export default {
|
||||
const amount = reward.type === 'GOLD' ? reward.content : reward.quantity
|
||||
return `${reward.name || typeName} (${amount || '-'})`
|
||||
},
|
||||
contentLabel(type) {
|
||||
return type === 'GOLD'
|
||||
? this.$t('pages.rocketConfig.goldAmount')
|
||||
: this.$t('pages.rocketConfig.resourceId')
|
||||
},
|
||||
openEdit(row) {
|
||||
// 深拷贝奖励池,避免弹窗中的编辑直接污染列表数据。
|
||||
const rewardConfig = (row.rewardConfig || []).map(item => Object.assign(createEmptyReward(), JSON.parse(JSON.stringify(item))))
|
||||
@ -319,11 +324,55 @@ export default {
|
||||
this.form.rewardConfig.splice(index, 1)
|
||||
},
|
||||
handleRewardTypeChange(reward) {
|
||||
// 切换发奖方式后清空上一次选择,防止资源和奖励类型错配。
|
||||
reward.id = null
|
||||
reward.content = ''
|
||||
reward.name = ''
|
||||
reward.cover = ''
|
||||
reward.sourceUrl = ''
|
||||
reward.detailType = ''
|
||||
if (reward.type === 'GOLD') {
|
||||
reward.name = this.$t('pages.rocketConfig.types.gold')
|
||||
reward.quantity = 1
|
||||
reward.detailType = ''
|
||||
}
|
||||
},
|
||||
openPropsDrawer(index) {
|
||||
const reward = this.form.rewardConfig[index]
|
||||
const commonPropTypes = ['AVATAR_FRAME', 'RIDE', 'NOBLE_VIP', 'THEME', 'CHAT_BUBBLE', 'FLOAT_PICTURE', 'DATA_CARD']
|
||||
this.selectingRewardIndex = index
|
||||
this.drawerPropsTypes = reward.type === 'BADGE' ? ['BADGE'] : commonPropTypes
|
||||
this.drawerPropsType = this.drawerPropsTypes.includes(reward.detailType)
|
||||
? reward.detailType
|
||||
: this.drawerPropsTypes[0]
|
||||
this.propsDrawerVisible = true
|
||||
},
|
||||
closePropsDrawer() {
|
||||
this.propsDrawerVisible = false
|
||||
this.selectingRewardIndex = -1
|
||||
},
|
||||
selectPropsSource(item) {
|
||||
const reward = this.form.rewardConfig[this.selectingRewardIndex]
|
||||
if (!reward) {
|
||||
this.closePropsDrawer()
|
||||
return
|
||||
}
|
||||
|
||||
// 抽屉选择是资源元数据的唯一来源,运营无需接触内部 ID、类型码和文件地址。
|
||||
reward.id = item.id
|
||||
reward.content = String(item.id)
|
||||
reward.name = item.name
|
||||
reward.cover = item.cover || ''
|
||||
reward.sourceUrl = item.sourceUrl || ''
|
||||
reward.detailType = item.type
|
||||
const field = `rewardConfig.${this.selectingRewardIndex}.content`
|
||||
this.propsDrawerVisible = false
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.configForm && this.$refs.configForm.clearValidate) {
|
||||
this.$refs.configForm.clearValidate(field)
|
||||
}
|
||||
this.selectingRewardIndex = -1
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
if (this.form.rewardConfig.length === 0) {
|
||||
this.$message.warning(this.$t('pages.rocketConfig.rewardRequired'))
|
||||
@ -406,6 +455,38 @@ export default {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.selected-prop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.selected-prop__cover {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
margin-right: 10px;
|
||||
border-radius: 4px;
|
||||
background: #f2f3f5;
|
||||
}
|
||||
|
||||
.selected-prop__empty-cover {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.selected-prop__name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.danger-text {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
@ -2,6 +2,11 @@ import Vue from 'vue'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import RocketConfig from '@/views/rocket/config/index.vue'
|
||||
|
||||
jest.mock('@/components/data/PropsSourceSelectDrawer', () => ({
|
||||
name: 'PropsSourceSelectDrawer',
|
||||
render: h => h('div')
|
||||
}))
|
||||
|
||||
jest.mock('@/api/rocket-config', () => ({
|
||||
listRocketConfigs: jest.fn(() => Promise.resolve({ body: [] })),
|
||||
updateRocketConfig: jest.fn(() => Promise.resolve({}))
|
||||
@ -42,4 +47,63 @@ describe('RocketConfig edit dialog', () => {
|
||||
expect(wrapper.vm.form.rewardConfig[0].type).toBe('GOLD')
|
||||
expect(wrapper.find('.rocket-dialog-content').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('fills all internal reward fields from the prop drawer selection', async() => {
|
||||
const wrapper = createWrapper()
|
||||
wrapper.vm.form.rewardConfig.push({
|
||||
id: null,
|
||||
type: 'PROPS',
|
||||
content: '',
|
||||
name: '',
|
||||
quantity: 7,
|
||||
detailType: '',
|
||||
cover: '',
|
||||
sourceUrl: ''
|
||||
})
|
||||
|
||||
wrapper.vm.openPropsDrawer(0)
|
||||
expect(wrapper.vm.propsDrawerVisible).toBe(true)
|
||||
expect(wrapper.vm.drawerPropsTypes).toContain('AVATAR_FRAME')
|
||||
|
||||
wrapper.vm.selectPropsSource({
|
||||
id: '123456',
|
||||
type: 'AVATAR_FRAME',
|
||||
name: '星空头像框',
|
||||
cover: 'https://cdn.example.com/cover.png',
|
||||
sourceUrl: 'https://cdn.example.com/source.svga'
|
||||
})
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.vm.form.rewardConfig[0]).toEqual({
|
||||
id: '123456',
|
||||
type: 'PROPS',
|
||||
content: '123456',
|
||||
name: '星空头像框',
|
||||
quantity: 7,
|
||||
detailType: 'AVATAR_FRAME',
|
||||
cover: 'https://cdn.example.com/cover.png',
|
||||
sourceUrl: 'https://cdn.example.com/source.svga'
|
||||
})
|
||||
expect(wrapper.vm.propsDrawerVisible).toBe(false)
|
||||
})
|
||||
|
||||
it('does not render manual resource metadata inputs for prop rewards', async() => {
|
||||
const wrapper = createWrapper()
|
||||
wrapper.vm.form.rewardConfig.push({
|
||||
id: '123456',
|
||||
type: 'PROPS',
|
||||
content: '123456',
|
||||
name: '星空头像框',
|
||||
quantity: 7,
|
||||
detailType: 'AVATAR_FRAME',
|
||||
cover: 'https://cdn.example.com/cover.png',
|
||||
sourceUrl: ''
|
||||
})
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.find('.selected-prop').exists()).toBe(true)
|
||||
expect(wrapper.text()).not.toContain('pages.rocketConfig.resourceId')
|
||||
expect(wrapper.text()).not.toContain('pages.rocketConfig.detailType')
|
||||
expect(wrapper.text()).not.toContain('pages.rocketConfig.cover')
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user