8 changed files with 494 additions and 1 deletions
@ -1 +1,2 @@ |
|||
export { useTemplateContentsApi } from './useTemplateContentsApi'; |
|||
export { useTemplateDefinitionsApi } from './useTemplateDefinitionsApi'; |
|||
|
|||
@ -0,0 +1,69 @@ |
|||
import type { |
|||
TextTemplateContentDto, |
|||
TextTemplateContentGetInput, |
|||
TextTemplateContentUpdateDto, |
|||
TextTemplateRestoreInput, |
|||
} from '../types/contents'; |
|||
|
|||
import { useRequest } from '@abp/request'; |
|||
|
|||
export function useTemplateContentsApi() { |
|||
const { cancel, request } = useRequest(); |
|||
/** |
|||
* 获取模板内容 |
|||
* @param input 参数 |
|||
* @returns 模板内容数据传输对象 |
|||
*/ |
|||
function getApi( |
|||
input: TextTemplateContentGetInput, |
|||
): Promise<TextTemplateContentDto> { |
|||
let url = '/api/text-templating/templates/content'; |
|||
url += input.culture ? `/${input.culture}/${input.name}` : `/${input.name}`; |
|||
return request<TextTemplateContentDto>(url, { |
|||
method: 'GET', |
|||
}); |
|||
} |
|||
/** |
|||
* 重置模板内容为默认值 |
|||
* @param name 模板名称 |
|||
* @param input 参数 |
|||
* @returns 模板定义数据传输对象列表 |
|||
*/ |
|||
function restoreToDefaultApi( |
|||
name: string, |
|||
input: TextTemplateRestoreInput, |
|||
): Promise<void> { |
|||
return request( |
|||
`/api/text-templating/templates/content/${name}/restore-to-default`, |
|||
{ |
|||
data: input, |
|||
method: 'PUT', |
|||
}, |
|||
); |
|||
} |
|||
/** |
|||
* 更新模板内容 |
|||
* @param name 模板名称 |
|||
* @param input 参数 |
|||
* @returns 模板内容数据传输对象 |
|||
*/ |
|||
function updateApi( |
|||
name: string, |
|||
input: TextTemplateContentUpdateDto, |
|||
): Promise<TextTemplateContentDto> { |
|||
return request<TextTemplateContentDto>( |
|||
`/api/text-templating/templates/content/${name}`, |
|||
{ |
|||
data: input, |
|||
method: 'PUT', |
|||
}, |
|||
); |
|||
} |
|||
|
|||
return { |
|||
cancel, |
|||
getApi, |
|||
restoreToDefaultApi, |
|||
updateApi, |
|||
}; |
|||
} |
|||
@ -0,0 +1,216 @@ |
|||
<script setup lang="ts"> |
|||
import type { ExtendedFormApi } from '@vben/common-ui'; |
|||
|
|||
import type { TextTemplateContentDto } from '../../types'; |
|||
import type { TextTemplateDefinitionDto } from '../../types/definitions'; |
|||
|
|||
import { useVbenForm, useVbenModal } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { useAbpStore } from '@abp/core'; |
|||
import { Modal as AntdvModal, Button, Card, message } from 'ant-design-vue'; |
|||
|
|||
import { useTemplateContentsApi } from '../../api'; |
|||
|
|||
defineProps<{ |
|||
title: string; |
|||
}>(); |
|||
const emits = defineEmits<{ |
|||
(event: 'change', data: TextTemplateContentDto): void; |
|||
}>(); |
|||
const abpStore = useAbpStore(); |
|||
const { getApi, restoreToDefaultApi, updateApi } = useTemplateContentsApi(); |
|||
|
|||
const [SourceForm, sourceFormApi] = useVbenForm({ |
|||
commonConfig: { |
|||
colon: true, |
|||
componentProps: { |
|||
class: 'w-full', |
|||
}, |
|||
disabled: true, |
|||
}, |
|||
layout: 'vertical', |
|||
schema: [ |
|||
{ |
|||
component: 'Select', |
|||
componentProps: { |
|||
fieldNames: { |
|||
label: 'displayName', |
|||
value: 'cultureName', |
|||
}, |
|||
onChange: (val?: string) => onCultureChange(sourceFormApi, val), |
|||
}, |
|||
fieldName: 'culture', |
|||
label: $t('AbpTextTemplating.BaseCultureName'), |
|||
rules: 'required', |
|||
}, |
|||
{ |
|||
component: 'Textarea', |
|||
componentProps: { |
|||
autoSize: { |
|||
minRows: 20, |
|||
}, |
|||
showCount: true, |
|||
}, |
|||
fieldName: 'content', |
|||
label: $t('AbpTextTemplating.BaseContent'), |
|||
rules: 'required', |
|||
}, |
|||
], |
|||
showDefaultActions: false, |
|||
}); |
|||
const [TargetForm, targetFormApi] = useVbenForm({ |
|||
commonConfig: { |
|||
colon: true, |
|||
componentProps: { |
|||
class: 'w-full', |
|||
}, |
|||
}, |
|||
handleSubmit: onSubmit, |
|||
layout: 'vertical', |
|||
schema: [ |
|||
{ |
|||
component: 'Select', |
|||
componentProps: { |
|||
fieldNames: { |
|||
label: 'displayName', |
|||
value: 'cultureName', |
|||
}, |
|||
onChange: (val?: string) => onCultureChange(targetFormApi, val), |
|||
}, |
|||
fieldName: 'culture', |
|||
label: $t('AbpTextTemplating.TargetCultureName'), |
|||
rules: 'required', |
|||
}, |
|||
{ |
|||
component: 'Textarea', |
|||
componentProps: { |
|||
autoSize: { |
|||
minRows: 20, |
|||
}, |
|||
showCount: true, |
|||
}, |
|||
fieldName: 'content', |
|||
label: $t('AbpTextTemplating.TargetContent'), |
|||
rules: 'required', |
|||
}, |
|||
], |
|||
showDefaultActions: false, |
|||
}); |
|||
const [Modal, modalApi] = useVbenModal({ |
|||
confirmText: $t('AbpTextTemplating.SaveContent'), |
|||
fullscreen: true, |
|||
fullscreenButton: false, |
|||
async onConfirm() { |
|||
await targetFormApi.validateAndSubmitForm(); |
|||
}, |
|||
async onOpenChange(isOpen) { |
|||
if (isOpen) { |
|||
await onInit(); |
|||
} |
|||
}, |
|||
}); |
|||
|
|||
async function onInit() { |
|||
try { |
|||
modalApi.setState({ loading: true }); |
|||
const culture = |
|||
abpStore.application?.localization.currentCulture.cultureName; |
|||
const languages = abpStore.application?.localization.languages ?? []; |
|||
sourceFormApi.updateSchema([ |
|||
{ |
|||
componentProps: { |
|||
options: languages, |
|||
}, |
|||
fieldName: 'culture', |
|||
}, |
|||
]); |
|||
targetFormApi.updateSchema([ |
|||
{ |
|||
componentProps: { |
|||
options: languages, |
|||
}, |
|||
fieldName: 'culture', |
|||
}, |
|||
]); |
|||
const { name } = modalApi.getData<TextTemplateDefinitionDto>(); |
|||
const { content } = await getApi({ |
|||
culture, |
|||
name, |
|||
}); |
|||
sourceFormApi.setValues({ |
|||
content, |
|||
culture, |
|||
}); |
|||
} finally { |
|||
modalApi.setState({ loading: false }); |
|||
} |
|||
} |
|||
|
|||
async function onCultureChange(formApi: ExtendedFormApi, culture?: string) { |
|||
const { name } = modalApi.getData<TextTemplateDefinitionDto>(); |
|||
const { content } = await getApi({ |
|||
culture, |
|||
name, |
|||
}); |
|||
formApi.setValues({ |
|||
content, |
|||
culture, |
|||
}); |
|||
} |
|||
|
|||
async function onSubmit(values: Record<string, string>) { |
|||
try { |
|||
modalApi.setState({ submitting: true }); |
|||
const { name } = modalApi.getData<TextTemplateDefinitionDto>(); |
|||
const dto = await updateApi(name, { |
|||
content: values.content!, |
|||
culture: values.culture, |
|||
}); |
|||
emits('change', dto); |
|||
message.success($t('AbpTextTemplating.TemplateContentUpdated')); |
|||
modalApi.close(); |
|||
} finally { |
|||
modalApi.setState({ submitting: false }); |
|||
} |
|||
} |
|||
|
|||
function onRestoreToDefault() { |
|||
AntdvModal.confirm({ |
|||
centered: true, |
|||
content: $t('AbpTextTemplating.RestoreToDefaultMessage'), |
|||
onOk: async () => { |
|||
const { name } = modalApi.getData<TextTemplateDefinitionDto>(); |
|||
const formValues = await sourceFormApi.getValues(); |
|||
await restoreToDefaultApi(name, { |
|||
culture: formValues.culture, |
|||
}); |
|||
message.success($t('AbpTextTemplating.TemplateContentRestoredToDefault')); |
|||
await onInit(); |
|||
}, |
|||
title: $t('AbpTextTemplating.RestoreToDefault'), |
|||
}); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Modal :title="$t('AbpTextTemplating.EditContents')"> |
|||
<Card :title="title"> |
|||
<template #extra> |
|||
<Button danger type="primary" @click="onRestoreToDefault"> |
|||
{{ $t('AbpTextTemplating.RestoreToDefault') }} |
|||
</Button> |
|||
</template> |
|||
<div class="flex flex-row gap-3"> |
|||
<div class="w-1/2"> |
|||
<SourceForm /> |
|||
</div> |
|||
<div class="w-1/2"> |
|||
<TargetForm /> |
|||
</div> |
|||
</div> |
|||
</Card> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,171 @@ |
|||
<script setup lang="ts"> |
|||
import type { |
|||
TextTemplateContentDto, |
|||
TextTemplateDefinitionDto, |
|||
} from '../../types'; |
|||
|
|||
import { computed, defineAsyncComponent, ref } from 'vue'; |
|||
|
|||
import { useVbenForm, useVbenModal } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { MarkdownViewer } from '@abp/components/vditor'; |
|||
import { useAbpStore } from '@abp/core'; |
|||
import { |
|||
Alert, |
|||
Modal as AntdvModal, |
|||
Button, |
|||
Card, |
|||
message, |
|||
} from 'ant-design-vue'; |
|||
|
|||
import { useTemplateContentsApi } from '../../api/useTemplateContentsApi'; |
|||
|
|||
const emits = defineEmits<{ |
|||
(event: 'change', data: TextTemplateContentDto): void; |
|||
}>(); |
|||
|
|||
const { cancel, getApi, restoreToDefaultApi, updateApi } = |
|||
useTemplateContentsApi(); |
|||
|
|||
const abpStore = useAbpStore(); |
|||
const textTemplate = ref<TextTemplateDefinitionDto>(); |
|||
|
|||
const getCardTitle = computed(() => { |
|||
if (!textTemplate.value) { |
|||
return ''; |
|||
} |
|||
return `${$t('AbpTextTemplating.DisplayName:Name')} - ${textTemplate.value.name}`; |
|||
}); |
|||
|
|||
const [Form, formApi] = useVbenForm({ |
|||
commonConfig: { |
|||
componentProps: { |
|||
class: 'w-full', |
|||
}, |
|||
}, |
|||
handleSubmit: onSubmit, |
|||
layout: 'vertical', |
|||
schema: [ |
|||
{ |
|||
component: 'Textarea', |
|||
componentProps: { |
|||
autoSize: { |
|||
minRows: 20, |
|||
}, |
|||
showCount: true, |
|||
}, |
|||
fieldName: 'content', |
|||
label: $t('AbpTextTemplating.DisplayName:Content'), |
|||
rules: 'required', |
|||
}, |
|||
], |
|||
showDefaultActions: false, |
|||
}); |
|||
const [Modal, modalApi] = useVbenModal({ |
|||
confirmText: $t('AbpTextTemplating.SaveContent'), |
|||
fullscreen: true, |
|||
fullscreenButton: false, |
|||
onCancel() { |
|||
modalApi.close(); |
|||
}, |
|||
onClosed() { |
|||
cancel('TemplateDefinitionModal has closed!'); |
|||
}, |
|||
onConfirm: async () => { |
|||
await formApi.validateAndSubmitForm(); |
|||
}, |
|||
onOpenChange: async (isOpen: boolean) => { |
|||
textTemplate.value = undefined; |
|||
if (isOpen) { |
|||
const textTemplateDefine = modalApi.getData<TextTemplateDefinitionDto>(); |
|||
textTemplate.value = textTemplateDefine; |
|||
await onGet(); |
|||
} |
|||
}, |
|||
}); |
|||
const [TemplateContentCurtuleModal, curtuleModalApi] = useVbenModal({ |
|||
connectedComponent: defineAsyncComponent( |
|||
() => import('./TemplateContentCurtuleModal.vue'), |
|||
), |
|||
}); |
|||
|
|||
async function onGet() { |
|||
try { |
|||
modalApi.setState({ loading: true }); |
|||
const textTemplateDefine = modalApi.getData<TextTemplateDefinitionDto>(); |
|||
const culture = textTemplateDefine.isInlineLocalized |
|||
? undefined |
|||
: abpStore.application?.localization.currentCulture.cultureName; |
|||
const dto = await getApi({ culture, name: textTemplateDefine.name }); |
|||
formApi.setFieldValue('content', dto.content); |
|||
} finally { |
|||
modalApi.setState({ loading: false }); |
|||
} |
|||
} |
|||
|
|||
function onRestoreToDefault() { |
|||
AntdvModal.confirm({ |
|||
centered: true, |
|||
content: $t('AbpTextTemplating.RestoreToDefaultMessage'), |
|||
onOk: async () => { |
|||
await restoreToDefaultApi(textTemplate.value!.name, {}); |
|||
message.success($t('AbpTextTemplating.TemplateContentRestoredToDefault')); |
|||
await onGet(); |
|||
}, |
|||
title: $t('AbpTextTemplating.RestoreToDefault'), |
|||
}); |
|||
} |
|||
|
|||
function onCustomizePerCulture() { |
|||
curtuleModalApi.setData(textTemplate.value); |
|||
curtuleModalApi.open(); |
|||
} |
|||
|
|||
async function onSubmit(values: Record<string, string>) { |
|||
try { |
|||
modalApi.setState({ submitting: true }); |
|||
const textTemplateDefine = modalApi.getData<TextTemplateDefinitionDto>(); |
|||
const culture = textTemplateDefine.isInlineLocalized |
|||
? undefined |
|||
: abpStore.application?.localization.currentCulture.cultureName; |
|||
const dto = await updateApi(textTemplateDefine.name, { |
|||
content: values.content!, |
|||
culture, |
|||
}); |
|||
emits('change', dto); |
|||
message.success($t('AbpTextTemplating.TemplateContentUpdated')); |
|||
modalApi.close(); |
|||
} finally { |
|||
modalApi.setState({ submitting: false }); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Modal :title="$t('AbpTextTemplating.EditContents')"> |
|||
<Alert v-if="textTemplate?.isInlineLocalized" type="warning"> |
|||
<template #message> |
|||
<MarkdownViewer |
|||
:value="$t('AbpTextTemplating.InlineContentDescription')" |
|||
/> |
|||
</template> |
|||
</Alert> |
|||
<Card :title="getCardTitle"> |
|||
<template #extra> |
|||
<div class="flex flex-row gap-2"> |
|||
<Button danger type="primary" @click="onRestoreToDefault"> |
|||
{{ $t('AbpTextTemplating.RestoreToDefault') }} |
|||
</Button> |
|||
<Button type="dashed" @click="onCustomizePerCulture"> |
|||
{{ $t('AbpTextTemplating.CustomizePerCulture') }} |
|||
</Button> |
|||
</div> |
|||
</template> |
|||
<Form /> |
|||
</Card> |
|||
<TemplateContentCurtuleModal :title="getCardTitle" /> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,26 @@ |
|||
interface TextTemplateContentDto { |
|||
content?: string; |
|||
culture?: string; |
|||
name: string; |
|||
} |
|||
|
|||
interface TextTemplateContentGetInput { |
|||
culture?: string; |
|||
name: string; |
|||
} |
|||
|
|||
interface TextTemplateRestoreInput { |
|||
culture?: string; |
|||
} |
|||
|
|||
interface TextTemplateContentUpdateDto { |
|||
content: string; |
|||
culture?: string; |
|||
} |
|||
|
|||
export type { |
|||
TextTemplateContentDto, |
|||
TextTemplateContentGetInput, |
|||
TextTemplateContentUpdateDto, |
|||
TextTemplateRestoreInput, |
|||
}; |
|||
@ -1 +1,2 @@ |
|||
export * from './contents'; |
|||
export * from './definitions'; |
|||
|
|||
Loading…
Reference in new issue