修改下拉
This commit is contained in:
parent
877f4f04fe
commit
4b24e94041
@ -214,6 +214,12 @@ export async function getUserBaseInfo(userId: number | string) {
|
||||
return requestClient.get<Record<string, any>>(`/user/base/info/${userId}`);
|
||||
}
|
||||
|
||||
export async function getUserPhotoWallNormal(userId: number | string) {
|
||||
return requestClient.get<Record<string, any>[]>(
|
||||
`/user/photo/wall/normal/${userId}`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getUserPhotoWallAll(userId: number | string) {
|
||||
return requestClient.get<string[]>(`/user/photo/wall/all/${userId}`);
|
||||
}
|
||||
|
||||
@ -125,6 +125,7 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||
|
||||
export const requestClient = createRequestClient(apiURL, {
|
||||
responseReturn: 'data',
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
export const baseRequestClient = requestClient;
|
||||
|
||||
@ -16,7 +16,6 @@ import {
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
SelectOption,
|
||||
Space,
|
||||
TextArea,
|
||||
message,
|
||||
@ -71,6 +70,16 @@ const actionOptions = computed(() => {
|
||||
);
|
||||
return filtered.length > 0 ? filtered : items;
|
||||
});
|
||||
const actionSelectOptions = computed(() =>
|
||||
actionOptions.value.map((item) => ({
|
||||
label: `${item.group} / ${item.label}`,
|
||||
value: item.value,
|
||||
})),
|
||||
);
|
||||
const dayOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 30].map((day) => ({
|
||||
label: `${day}天`,
|
||||
value: day,
|
||||
}));
|
||||
|
||||
const form = reactive({
|
||||
accountStatusEnum: '',
|
||||
@ -176,25 +185,22 @@ async function handleSubmit() {
|
||||
<Input :value="currentStatus" disabled />
|
||||
</FormItem>
|
||||
<FormItem label="处理状态">
|
||||
<Select option-label-prop="children" v-model:value="form.accountStatusEnum" :disabled="loading">
|
||||
<SelectOption
|
||||
v-for="item in actionOptions"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.group }} / {{ item.label }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<Select
|
||||
v-model:value="form.accountStatusEnum"
|
||||
:disabled="loading"
|
||||
:options="actionSelectOptions"
|
||||
option-label-prop="label"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="form.accountStatusEnum === 'FREEZE' || form.accountStatusEnum === 'ARCHIVE' || form.accountStatusEnum === 'ARCHIVE1DAY'"
|
||||
label="天数"
|
||||
>
|
||||
<Select option-label-prop="children" v-model:value="form.days">
|
||||
<SelectOption v-for="day in [1,2,3,4,5,6,7,8,9,10,15,30]" :key="day" :value="day">
|
||||
{{ day }}天
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<Select
|
||||
v-model:value="form.days"
|
||||
:options="dayOptions"
|
||||
option-label-prop="label"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="备注">
|
||||
<TextArea v-model:value="form.description" :rows="4" />
|
||||
|
||||
@ -7,7 +7,7 @@ import { Page } from '@vben/common-ui';
|
||||
import {
|
||||
getUserAccountStatus,
|
||||
getUserBaseInfo,
|
||||
getUserPhotoWallAll,
|
||||
getUserPhotoWallNormal,
|
||||
getUserRegisterInfo,
|
||||
} from '#/api/legacy/approval';
|
||||
|
||||
@ -64,7 +64,7 @@ watch(
|
||||
getUserBaseInfo(id),
|
||||
getUserRegisterInfo(id),
|
||||
getUserAccountStatus(id),
|
||||
getUserPhotoWallAll(id),
|
||||
getUserPhotoWallNormal(id),
|
||||
]);
|
||||
baseInfo.value = base || {};
|
||||
registerInfo.value = register || {};
|
||||
|
||||
@ -0,0 +1,209 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import ActivityTemplateLangEditor from './activity-template-lang-editor.vue';
|
||||
import { cloneTemplateValue } from './activity-template-shared';
|
||||
|
||||
import {
|
||||
Empty,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
Select,
|
||||
Tabs,
|
||||
TabPane,
|
||||
} from 'antdv-next';
|
||||
|
||||
const props = defineProps<{
|
||||
form: null | Record<string, any>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [Record<string, any>];
|
||||
}>();
|
||||
|
||||
const syncing = ref(false);
|
||||
const formState = ref<null | Record<string, any>>(null);
|
||||
const activeTab = ref('attr');
|
||||
|
||||
const backgroundColorDirections = [
|
||||
{ label: '无', value: 'NONE' },
|
||||
{ label: '从左到右渐变', value: 'to right' },
|
||||
{ label: '从右到左渐变', value: 'to left' },
|
||||
{ label: '从下到上渐变', value: 'to top' },
|
||||
{ label: '从上到下渐变', value: 'to bottom' },
|
||||
{ label: '从左对角渐变', value: 'to bottom right' },
|
||||
{ label: '从右对角渐变', value: 'to bottom left' },
|
||||
];
|
||||
|
||||
const backgroundImgRepeats = [
|
||||
{ label: 'repeat', value: 'repeat' },
|
||||
{ label: 'repeat-x', value: 'repeat-x' },
|
||||
{ label: 'repeat-y', value: 'repeat-y' },
|
||||
{ label: 'no-repeat', value: 'no-repeat' },
|
||||
{ label: 'inherit', value: 'inherit' },
|
||||
];
|
||||
|
||||
const attrForm = computed(() => formState.value?.attr || {});
|
||||
const styleForm = computed(() => formState.value?.style || {});
|
||||
|
||||
watch(
|
||||
() => props.form,
|
||||
(value) => {
|
||||
syncing.value = true;
|
||||
formState.value = value ? cloneTemplateValue(value) : null;
|
||||
Promise.resolve().then(() => {
|
||||
syncing.value = false;
|
||||
});
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
formState,
|
||||
(value) => {
|
||||
if (syncing.value || !value) {
|
||||
return;
|
||||
}
|
||||
emit('change', cloneTemplateValue(value));
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
function hasPath(value: Record<string, any>, path: string) {
|
||||
if (!value || !path) {
|
||||
return false;
|
||||
}
|
||||
const keys = path.split('.').filter(Boolean);
|
||||
let current: any = value;
|
||||
for (const key of keys) {
|
||||
if (current == null || !(key in current)) {
|
||||
return false;
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="attr-editor">
|
||||
<Empty v-if="!formState" description="请选择模板节点" />
|
||||
|
||||
<Tabs v-else v-model:activeKey="activeTab" size="small">
|
||||
<TabPane key="attr" tab="属性">
|
||||
<Form layout="vertical">
|
||||
<FormItem v-if="attrForm.id" label="ID">
|
||||
<Input :value="String(attrForm.id || '')" disabled />
|
||||
</FormItem>
|
||||
<FormItem v-if="attrForm.label" label="组件名称">
|
||||
<Input :value="String(attrForm.label || '')" disabled />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="hasPath(attrForm, 'copywriting')"
|
||||
:label="String(attrForm.copywriting?.label || '文案')"
|
||||
>
|
||||
<ActivityTemplateLangEditor
|
||||
v-model:modelValue="attrForm.copywriting.i18n"
|
||||
:type="attrForm.copywriting?.type"
|
||||
/>
|
||||
</FormItem>
|
||||
<template v-if="Array.isArray(attrForm.imgUrls)">
|
||||
<FormItem
|
||||
v-for="(item, index) in attrForm.imgUrls"
|
||||
:key="`${item.id || index}`"
|
||||
:label="item.label || `图片${Number(index) + 1}`"
|
||||
>
|
||||
<Input v-model:value="item.url" />
|
||||
</FormItem>
|
||||
</template>
|
||||
</Form>
|
||||
</TabPane>
|
||||
|
||||
<TabPane v-if="!styleForm.hide" key="style" tab="样式">
|
||||
<div v-if="styleForm.tips" class="style-tips">
|
||||
{{ styleForm.tips }}
|
||||
</div>
|
||||
|
||||
<Form layout="vertical">
|
||||
<div class="section-title">背景</div>
|
||||
<FormItem v-if="hasPath(styleForm, 'background.img.url')" label="背景图">
|
||||
<Input v-model:value="styleForm.background.img.url" />
|
||||
</FormItem>
|
||||
<FormItem v-if="hasPath(styleForm, 'background.img.repeat')" label="背景图重复">
|
||||
<Select
|
||||
v-model:value="styleForm.background.img.repeat"
|
||||
allow-clear
|
||||
:options="backgroundImgRepeats"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem v-if="hasPath(styleForm, 'background.img.size')" label="背景图大小">
|
||||
<Input v-model:value="styleForm.background.img.size" placeholder="如 100% 100%" />
|
||||
</FormItem>
|
||||
<FormItem v-if="hasPath(styleForm, 'background.color.start')" label="背景起始色">
|
||||
<Input v-model:value="styleForm.background.color.start" placeholder="#FFFFFF" />
|
||||
</FormItem>
|
||||
<FormItem v-if="hasPath(styleForm, 'background.color.direction')" label="渐变方向">
|
||||
<Select v-model:value="styleForm.background.color.direction" :options="backgroundColorDirections" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="
|
||||
hasPath(styleForm, 'background.color.end')
|
||||
&& styleForm.background?.color?.direction !== 'NONE'
|
||||
"
|
||||
label="背景结束色"
|
||||
>
|
||||
<Input v-model:value="styleForm.background.color.end" placeholder="#000000" />
|
||||
</FormItem>
|
||||
|
||||
<div class="section-title">字体</div>
|
||||
<FormItem v-if="hasPath(styleForm, 'font.color')" label="字体颜色">
|
||||
<Input v-model:value="styleForm.font.color" placeholder="#FFFFFF" />
|
||||
</FormItem>
|
||||
<FormItem v-if="hasPath(styleForm, 'font.size')" label="字体大小">
|
||||
<Input v-model:value="styleForm.font.size" placeholder=".35rem" />
|
||||
</FormItem>
|
||||
<FormItem v-if="hasPath(styleForm, 'font.weight')" label="字体粗细">
|
||||
<Input v-model:value="styleForm.font.weight" placeholder="bold / 600" />
|
||||
</FormItem>
|
||||
|
||||
<div class="section-title">边框</div>
|
||||
<FormItem v-if="hasPath(styleForm, 'border.width')" label="边框宽度">
|
||||
<Input v-model:value="styleForm.border.width" placeholder=".02rem" />
|
||||
</FormItem>
|
||||
<FormItem v-if="hasPath(styleForm, 'border.style')" label="边框样式">
|
||||
<Input v-model:value="styleForm.border.style" placeholder="solid" />
|
||||
</FormItem>
|
||||
<FormItem v-if="hasPath(styleForm, 'border.color')" label="边框颜色">
|
||||
<Input v-model:value="styleForm.border.color" placeholder="#FFFFFF" />
|
||||
</FormItem>
|
||||
<FormItem v-if="hasPath(styleForm, 'border.radius')" label="边框圆角">
|
||||
<Input v-model:value="styleForm.border.radius" placeholder=".1rem" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.attr-editor {
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
.style-tips {
|
||||
background: #eff6ff;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 10px;
|
||||
color: #1d4ed8;
|
||||
margin-bottom: 12px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
color: #0f172a;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
@ -6,15 +6,26 @@ import {
|
||||
updateTemplate,
|
||||
} from '#/api/legacy/system';
|
||||
|
||||
import ActivityTemplateAttrEditor from './activity-template-attr-editor.vue';
|
||||
import {
|
||||
buildTemplateNodeForm,
|
||||
cloneTemplateValue,
|
||||
findTemplateNode,
|
||||
getFirstTemplateNodeId,
|
||||
giftRankingTreeIndexs,
|
||||
normalizeTemplateTree,
|
||||
} from './activity-template-shared';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Descriptions,
|
||||
DescriptionsItem,
|
||||
Drawer,
|
||||
Empty,
|
||||
Modal,
|
||||
Space,
|
||||
Spin,
|
||||
TextArea,
|
||||
Tree,
|
||||
message,
|
||||
} from 'antdv-next';
|
||||
|
||||
@ -33,7 +44,8 @@ const emit = defineEmits<{
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const template = ref<Record<string, any> | null>(null);
|
||||
const jsonText = ref('');
|
||||
const indexTree = ref<Array<Record<string, any>>>([]);
|
||||
const selectedNodeId = ref('');
|
||||
|
||||
const title = computed(() => {
|
||||
if (!props.templateId) {
|
||||
@ -42,6 +54,20 @@ const title = computed(() => {
|
||||
return `编辑模版 #${props.templateId}`;
|
||||
});
|
||||
|
||||
const treeData = computed(() => {
|
||||
const convert = (list: Array<Record<string, any>>): Array<Record<string, any>> =>
|
||||
list.map((item) => ({
|
||||
children: convert(item.children || []),
|
||||
key: item.id,
|
||||
title: `${item.label || item.id} (${item.id})`,
|
||||
}));
|
||||
return convert(indexTree.value);
|
||||
});
|
||||
|
||||
const selectedNode = computed(() => findTemplateNode(indexTree.value, selectedNodeId.value));
|
||||
|
||||
const selectedNodeForm = computed(() => buildTemplateNodeForm(selectedNode.value));
|
||||
|
||||
watch(
|
||||
() => (props.open ? props.templateId : ''),
|
||||
async (templateId) => {
|
||||
@ -51,8 +77,17 @@ watch(
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await getTemplateById(templateId);
|
||||
template.value = result || {};
|
||||
jsonText.value = JSON.stringify(template.value?.indexTree || [], null, 2);
|
||||
const nextTree = normalizeTemplateTree(
|
||||
Array.isArray(result?.indexTree) && result.indexTree.length > 0
|
||||
? result.indexTree
|
||||
: giftRankingTreeIndexs,
|
||||
);
|
||||
template.value = {
|
||||
...(result || {}),
|
||||
indexTree: nextTree,
|
||||
};
|
||||
indexTree.value = nextTree;
|
||||
selectedNodeId.value = getFirstTemplateNodeId(nextTree);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@ -64,28 +99,17 @@ async function handleSave() {
|
||||
if (!template.value) {
|
||||
return;
|
||||
}
|
||||
let indexTree: any[] = [];
|
||||
try {
|
||||
const parsed = JSON.parse(jsonText.value || '[]');
|
||||
if (!Array.isArray(parsed)) {
|
||||
message.warning('indexTree 必须是数组');
|
||||
return;
|
||||
}
|
||||
indexTree = parsed;
|
||||
} catch {
|
||||
message.warning('JSON 格式不正确');
|
||||
return;
|
||||
}
|
||||
|
||||
saving.value = true;
|
||||
try {
|
||||
const nextTree = cloneTemplateValue(indexTree.value || []);
|
||||
await updateTemplate({
|
||||
...template.value,
|
||||
indexTree,
|
||||
indexTree: nextTree,
|
||||
});
|
||||
template.value = {
|
||||
...template.value,
|
||||
indexTree,
|
||||
indexTree: nextTree,
|
||||
};
|
||||
message.success('保存成功');
|
||||
emit('success');
|
||||
@ -93,6 +117,30 @@ async function handleSave() {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelect(keys: Array<string | number>) {
|
||||
selectedNodeId.value = String(keys?.[0] || '');
|
||||
}
|
||||
|
||||
function handleEditorChange(form: Record<string, any>) {
|
||||
const node = selectedNode.value;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
node.editor = cloneTemplateValue(form);
|
||||
}
|
||||
|
||||
function handleResetDefault() {
|
||||
Modal.confirm({
|
||||
title: '确认恢复默认模板树吗?',
|
||||
content: '会覆盖当前抽屉里未保存的模板结构。',
|
||||
onOk() {
|
||||
const nextTree = normalizeTemplateTree(giftRankingTreeIndexs);
|
||||
indexTree.value = nextTree;
|
||||
selectedNodeId.value = getFirstTemplateNodeId(nextTree);
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -118,16 +166,33 @@ async function handleSave() {
|
||||
</Descriptions>
|
||||
|
||||
<div class="tips">
|
||||
<div>原项目这里编辑的是模版 `indexTree` 结构。</div>
|
||||
<div>当前保持同一份 JSON 结构读写,不改动后台字段定义。</div>
|
||||
<div>新后台已恢复为树形模板编辑。</div>
|
||||
<div>左侧选择节点,右侧编辑属性和样式,保存时仍然写回同一份 `indexTree` 结构。</div>
|
||||
</div>
|
||||
|
||||
<TextArea
|
||||
v-model:value="jsonText"
|
||||
:auto-size="{ minRows: 24, maxRows: 36 }"
|
||||
class="json-editor"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<div class="toolbar">
|
||||
<Button @click="handleResetDefault">恢复默认模板树</Button>
|
||||
</div>
|
||||
|
||||
<div class="workspace">
|
||||
<div class="tree-panel">
|
||||
<div class="panel-title">模板树</div>
|
||||
<Tree
|
||||
:selected-keys="selectedNodeId ? [selectedNodeId] : []"
|
||||
:tree-data="treeData"
|
||||
block-node
|
||||
default-expand-all
|
||||
@select="handleSelect"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-panel">
|
||||
<div class="panel-title">
|
||||
{{ selectedNode ? `${selectedNode.label || '-'} (${selectedNode.id || '-'})` : '节点属性' }}
|
||||
</div>
|
||||
<ActivityTemplateAttrEditor :form="selectedNodeForm" @change="handleEditorChange" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Empty v-else description="未获取到模版数据" />
|
||||
</Spin>
|
||||
@ -154,6 +219,11 @@ async function handleSave() {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.tips {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
@ -163,14 +233,28 @@ async function handleSave() {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.json-editor {
|
||||
font-family:
|
||||
'SFMono-Regular',
|
||||
'Menlo',
|
||||
'Monaco',
|
||||
'Consolas',
|
||||
'Liberation Mono',
|
||||
'Courier New',
|
||||
monospace;
|
||||
.workspace {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: 320px minmax(0, 1fr);
|
||||
min-height: 560px;
|
||||
}
|
||||
|
||||
.tree-panel,
|
||||
.form-panel {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 14px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.tree-panel {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
color: #0f172a;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -0,0 +1,134 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import {
|
||||
cloneTemplateValue,
|
||||
langs,
|
||||
} from './activity-template-shared';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Select,
|
||||
TextArea,
|
||||
} from 'antdv-next';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: Array<Record<string, any>>;
|
||||
type?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [Array<Record<string, any>>];
|
||||
}>();
|
||||
|
||||
const syncing = ref(false);
|
||||
const langList = ref<Array<Record<string, any>>>([]);
|
||||
const pendingLang = ref<string>('');
|
||||
|
||||
const availableOptions = computed(() =>
|
||||
langs
|
||||
.filter((item) => !langList.value.some((langItem) => langItem.lang === item.value))
|
||||
.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.value,
|
||||
})),
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
syncing.value = true;
|
||||
langList.value = cloneTemplateValue(value || []);
|
||||
Promise.resolve().then(() => {
|
||||
syncing.value = false;
|
||||
});
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
langList,
|
||||
(value) => {
|
||||
if (syncing.value) {
|
||||
return;
|
||||
}
|
||||
emit('update:modelValue', cloneTemplateValue(value || []));
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
function addLang() {
|
||||
if (!pendingLang.value) {
|
||||
return;
|
||||
}
|
||||
const matched = langs.find((item) => item.value === pendingLang.value);
|
||||
if (!matched) {
|
||||
return;
|
||||
}
|
||||
langList.value.push({
|
||||
lang: matched.value,
|
||||
name: matched.name,
|
||||
value: '',
|
||||
});
|
||||
pendingLang.value = '';
|
||||
}
|
||||
|
||||
function removeLang(index: number) {
|
||||
langList.value.splice(index, 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="lang-editor">
|
||||
<div v-for="(item, index) in langList" :key="`${item.lang}-${index}`" class="lang-item">
|
||||
<div class="lang-item__head">
|
||||
<span>{{ item.name || item.lang }}</span>
|
||||
<Button danger size="small" type="link" @click="removeLang(index)">
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
<TextArea
|
||||
v-if="type === 'textarea'"
|
||||
v-model:value="item.value"
|
||||
:rows="4"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<Input v-else v-model:value="item.value" />
|
||||
</div>
|
||||
|
||||
<div v-if="availableOptions.length > 0" class="lang-editor__toolbar">
|
||||
<Select
|
||||
v-model:value="pendingLang"
|
||||
allow-clear
|
||||
placeholder="添加语言"
|
||||
style="width: 180px"
|
||||
:options="availableOptions"
|
||||
/>
|
||||
<Button @click="addLang">添加</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.lang-editor {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.lang-item {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.lang-item__head {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.lang-editor__toolbar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
26
apps/src/views/operate/components/activity-template-shared.d.ts
vendored
Normal file
26
apps/src/views/operate/components/activity-template-shared.d.ts
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
export declare const langs: Array<{ name: string; value: string }>;
|
||||
|
||||
export declare const giftRankingTreeIndexs: Array<Record<string, any>>;
|
||||
|
||||
export declare function cloneTemplateValue<T>(value: T): T;
|
||||
|
||||
export declare function createEditorForm(
|
||||
options?: Record<string, any>,
|
||||
): Record<string, any>;
|
||||
|
||||
export declare function normalizeTemplateTree(
|
||||
tree?: Array<Record<string, any>>,
|
||||
): Array<Record<string, any>>;
|
||||
|
||||
export declare function findTemplateNode(
|
||||
tree: Array<Record<string, any>>,
|
||||
nodeId: string,
|
||||
): null | Record<string, any>;
|
||||
|
||||
export declare function getFirstTemplateNodeId(
|
||||
tree?: Array<Record<string, any>>,
|
||||
): string;
|
||||
|
||||
export declare function buildTemplateNodeForm(
|
||||
node: null | Record<string, any>,
|
||||
): null | Record<string, any>;
|
||||
1101
apps/src/views/operate/components/activity-template-shared.js
Normal file
1101
apps/src/views/operate/components/activity-template-shared.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -39,6 +39,7 @@ const form = reactive({
|
||||
amount: undefined as number | undefined,
|
||||
reasonType: 5,
|
||||
remarks: '',
|
||||
usdAmount: undefined as number | undefined,
|
||||
});
|
||||
const rewardReasonOptions = [
|
||||
{ label: '奖励', value: 1 as any },
|
||||
@ -56,6 +57,15 @@ const deductReasonOptions = [
|
||||
const reasonTypeOptions = computed(() =>
|
||||
props.action === 'reward' ? rewardReasonOptions : deductReasonOptions,
|
||||
);
|
||||
const selectedReasonLabel = computed(() => {
|
||||
const matched = reasonTypeOptions.value.find(
|
||||
(item) => item.value === form.reasonType,
|
||||
);
|
||||
return matched?.label ?? '';
|
||||
});
|
||||
const needsUsdAmount = computed(
|
||||
() => props.type === 'gold' && props.action === 'reward' && [3, 4].includes(form.reasonType),
|
||||
);
|
||||
|
||||
const title = computed(() => {
|
||||
const typeName =
|
||||
@ -73,10 +83,17 @@ watch(
|
||||
form.amount = undefined;
|
||||
form.reasonType = props.action === 'reward' ? 5 : 4;
|
||||
form.remarks = '';
|
||||
form.usdAmount = undefined;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function buildRemarks() {
|
||||
const reasonLabel = selectedReasonLabel.value;
|
||||
const remarks = form.remarks.trim();
|
||||
return reasonLabel ? (remarks ? `${reasonLabel}/${remarks}` : reasonLabel) : remarks;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!props.userId) {
|
||||
return;
|
||||
@ -89,25 +106,33 @@ async function handleSubmit() {
|
||||
message.warning('请输入备注');
|
||||
return;
|
||||
}
|
||||
if (needsUsdAmount.value && (!form.usdAmount || form.usdAmount <= 0)) {
|
||||
message.warning('请输入大于 0 的美金数量');
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.type === 'gold') {
|
||||
const payload = {
|
||||
reason: form.reasonType,
|
||||
remarks: form.remarks.trim(),
|
||||
userId: props.userId,
|
||||
value: form.amount,
|
||||
};
|
||||
const remarks = buildRemarks();
|
||||
if (props.action === 'reward') {
|
||||
await sendGold(payload);
|
||||
await sendGold({
|
||||
quantity: form.amount,
|
||||
remarks,
|
||||
type: form.reasonType,
|
||||
usdQuantity: needsUsdAmount.value ? form.usdAmount : undefined,
|
||||
userId: props.userId,
|
||||
});
|
||||
} else {
|
||||
await deductGold(payload);
|
||||
await deductGold({
|
||||
quantity: form.amount,
|
||||
remarks,
|
||||
userId: props.userId,
|
||||
});
|
||||
}
|
||||
} else if (props.type === 'diamond') {
|
||||
const payload = {
|
||||
reason: form.reasonType,
|
||||
remarks: form.remarks.trim(),
|
||||
quantity: form.amount,
|
||||
remarks: buildRemarks(),
|
||||
userId: props.userId,
|
||||
value: form.amount,
|
||||
};
|
||||
if (props.action === 'reward') {
|
||||
await sendDiamond(payload);
|
||||
@ -117,14 +142,14 @@ async function handleSubmit() {
|
||||
} else if (props.action === 'reward') {
|
||||
await rewardGameCoupon({
|
||||
coupon: form.amount,
|
||||
remarks: form.remarks.trim(),
|
||||
remarks: buildRemarks(),
|
||||
rewardType: form.reasonType,
|
||||
userId: props.userId,
|
||||
});
|
||||
} else {
|
||||
await deductGameCoupon({
|
||||
coupon: form.amount,
|
||||
remarks: form.remarks.trim(),
|
||||
remarks: buildRemarks(),
|
||||
userId: props.userId,
|
||||
});
|
||||
}
|
||||
@ -155,6 +180,14 @@ async function handleSubmit() {
|
||||
style="width: 100%"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem v-if="needsUsdAmount" label="美金数量">
|
||||
<InputNumber
|
||||
v-model:value="form.usdAmount"
|
||||
:min="0.01"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="原因">
|
||||
<Select
|
||||
option-label-prop="label"
|
||||
|
||||
@ -63,7 +63,7 @@ class RequestClient {
|
||||
},
|
||||
responseReturn: 'raw',
|
||||
// 默认超时时间
|
||||
timeout: 10_000,
|
||||
timeout: 60_000,
|
||||
};
|
||||
const { ...axiosConfig } = options;
|
||||
const requestConfig = merge(axiosConfig, defaultConfig);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user