feat: 添加下载文档和显示数据按钮 (#1370)

Reviewed-on: daoyoucloud/tachybase#1370
Co-authored-by: wjh <wwwjh0710@163.com>
Co-committed-by: wjh <wwwjh0710@163.com>
This commit is contained in:
wjh 2024-07-25 10:53:27 +08:00 committed by sealday
parent bc563534a3
commit c3c8b5b655
11 changed files with 192 additions and 42 deletions

BIN
.;.swp Normal file

Binary file not shown.

View File

@ -831,5 +831,10 @@
"Load": "Load",
"Dump": "Dump",
"Chang on Parent": "Chang on Parent",
"Please enter search content": "Please enter search content"
"Please enter search content": "Please enter search content",
"Is DownLoad":"Is DownLoad",
"Show Data":"Show Data",
"Setting Down Title":"Setting Down Title",
"The current return type is not a document type":"The current return type is not a document type",
"Test Data":"Test Data"
}

View File

@ -934,5 +934,10 @@
"Sorry, the page you visited does not exist.": "对不起,您访问的页面不存在。",
"Back Home": "回到主页",
"Chang on Parent": "选择父级",
"Please enter search content": "请输入查询内容"
"Please enter search content": "请输入查询内容",
"Is DownLoad":"下载文件",
"Show Data":"显示数据",
"Setting Down Title":"设置文件名称",
"The current return type is not a document type":"当前返回类型不是文档类型",
"Test Data":"测试数据"
}

View File

@ -787,6 +787,102 @@ export function SecondConFirm() {
/>
);
}
export function IsDownLoad() {
const { dn } = useDesignable();
const fieldSchema = useFieldSchema();
const { t } = useTranslation();
return (
<SchemaSettingsSwitchItem
title={t('Is DownLoad')}
checked={!!fieldSchema?.['x-action-settings']?.onSuccess?.down}
onChange={(value) => {
if (!fieldSchema?.['x-action-settings']?.onSuccess) {
fieldSchema['x-action-settings'] = {
...fieldSchema?.['x-action-settings'],
onSuccess: {
down: false,
},
};
}
fieldSchema['x-action-settings'].onSuccess.down = value;
dn.emit('patch', {
schema: {
['x-uid']: fieldSchema['x-uid'],
'x-action-settings': { ...fieldSchema['x-action-settings'] },
},
});
}}
/>
);
}
export function ShowData() {
const { dn } = useDesignable();
const fieldSchema = useFieldSchema();
const { t } = useTranslation();
const field = useField();
return (
<SchemaSettingsSwitchItem
title={t('Show Data')}
checked={!!fieldSchema?.['x-component-props']?.showData}
onChange={(value) => {
if (!fieldSchema?.['x-component-props']) {
fieldSchema['x-component-props'] = { showData: false };
}
fieldSchema['x-component-props'].showData = value;
field.componentProps = fieldSchema['x-component-props'];
dn.emit('patch', {
schema: {
['x-uid']: fieldSchema['x-uid'],
'x-component-props': { ...fieldSchema['x-component-props'] },
},
});
}}
/>
);
}
export function SettingDownTitle() {
const fieldSchema = useFieldSchema();
const { dn } = useDesignable();
const { t } = useTranslation();
return (
<SchemaSettingsModalItem
title={t('Setting Down Title')}
onSubmit={({ title }) => {
if (!fieldSchema?.['x-action-settings']) {
fieldSchema['x-action-settings'] = {
onSussess: {
downTitle: title,
},
};
} else {
fieldSchema['x-action-settings']['onSuccess'] = {
...fieldSchema['x-action-settings']?.['onSuccess'],
downTitle: title,
};
}
dn.emit('patch', {
schema: {
['x-uid']: fieldSchema['x-uid'],
'x-action-settings': { ...fieldSchema['x-action-settings'] },
},
});
}}
schema={{
type: 'item',
title: t('Setting Down Title'),
properties: {
title: {
'x-component': 'Input',
default: fieldSchema['x-action-settings']?.['onSuccess']?.downTitle || 'document.docx',
},
},
}}
/>
);
}
/**
* @deprecated

View File

@ -12,6 +12,7 @@
"@tachybase/components": "workspace:*",
"@tachybase/schema": "workspace:*",
"antd": "5.19.1",
"file-saver": "^2.0.5",
"lodash": "~4.17.21",
"react-i18next": "^14.1.2",
"react-router-dom": "~6.25.1"

View File

@ -11,8 +11,11 @@ import { useField, useFieldSchema, useForm } from '@tachybase/schema';
import { isURL } from '@tachybase/utils/client';
import { App } from 'antd';
import { saveAs } from 'file-saver';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from '../locale';
export const useCustomizeRequestActionProps = () => {
const apiClient = useAPIClient();
const navigate = useNavigate();
@ -26,6 +29,7 @@ export const useCustomizeRequestActionProps = () => {
const actionField = useField();
const { setVisible } = useActionContext();
const { modal, message } = App.useApp();
const { t } = useTranslation();
return {
async onClick() {
const { skipValidator, onSuccess } = actionSchema?.['x-action-settings'] ?? {};
@ -42,9 +46,10 @@ export const useCustomizeRequestActionProps = () => {
actionField.data ??= {};
actionField.data.loading = true;
try {
await apiClient.request({
const res = (await apiClient.request({
url: `/customRequests:send/${fieldSchema['x-uid']}`,
method: 'POST',
responseType: onSuccess?.down ? 'blob' : 'json',
data: {
currentRecord: {
id: record[getPrimaryKey()],
@ -52,7 +57,16 @@ export const useCustomizeRequestActionProps = () => {
data: formValues,
},
},
});
})) as any;
const headerContentType = res.headers.getContentType();
if (onSuccess?.down) {
if (headerContentType === 'application/octet-stream') {
const downTitle = onSuccess.downTitle;
saveAs(res.data, downTitle);
} else {
message.error(t('The current return type is not a document type'));
}
}
actionField.data.loading = false;
if (!(resource instanceof TableFieldResource)) {
__parent?.service?.refresh?.();
@ -61,29 +75,39 @@ export const useCustomizeRequestActionProps = () => {
if (xAction === 'customize:form:request') {
setVisible?.(false);
}
if (!onSuccess?.successMessage) {
return;
}
if (onSuccess?.manualClose) {
modal.success({
title: compile(onSuccess?.successMessage),
onOk: async () => {
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
if (isURL(onSuccess.redirectTo)) {
window.location.href = onSuccess.redirectTo;
} else {
navigate(onSuccess.redirectTo);
}
}
},
if (actionSchema?.['x-component-props']?.showData) {
modal.confirm({
title: 'Test Data',
content: `${JSON.stringify(res.data)}`,
okText: t('Confirm'),
cancelText: t('Cancel'),
});
} else {
message.success(compile(onSuccess?.successMessage));
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
if (isURL(onSuccess.redirectTo)) {
window.location.href = onSuccess.redirectTo;
} else {
navigate(onSuccess.redirectTo);
if (!onSuccess?.successMessage) {
return;
}
if (onSuccess?.manualClose) {
modal.success({
title: compile(onSuccess?.successMessage),
onOk: async () => {
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
if (isURL(onSuccess.redirectTo)) {
window.location.href = onSuccess.redirectTo;
} else {
navigate(onSuccess.redirectTo);
}
}
},
});
} else {
message.success(compile(onSuccess?.successMessage));
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
if (isURL(onSuccess.redirectTo)) {
window.location.href = onSuccess.redirectTo;
} else {
navigate(onSuccess.redirectTo);
}
}
}
}

View File

@ -1,13 +1,17 @@
import {
AfterSuccess,
ButtonEditor,
IsDownLoad,
RemoveButton,
SchemaSettings,
SchemaSettingsLinkageRules,
SecondConFirm,
SettingDownTitle,
ShowData,
useCollection,
useSchemaToolbar,
} from '@tachybase/client';
import { useFieldSchema } from '@tachybase/schema';
import { CustomRequestACL, CustomRequestSettingsItem } from './components/CustomRequestActionDesigner';
@ -41,6 +45,23 @@ export const customizeCustomRequestActionSettings = new SchemaSettings({
name: 'secondConFirm',
Component: SecondConFirm,
},
{
name: 'isDownLoad',
Component: IsDownLoad,
},
{
name: 'settingDownTitle',
Component: SettingDownTitle,
useVisible() {
const fieldSchema = useFieldSchema();
const down = fieldSchema?.['x-action-settings']?.onSuccess?.down;
return down;
},
},
{
name: 'showData',
Component: ShowData,
},
{
name: 'afterSuccessfulSubmission',
Component: AfterSuccess,

View File

@ -1,3 +1,5 @@
import fs from 'fs';
import http from 'http';
import { Context, Next } from '@tachybase/actions';
import { appendArrayColumn } from '@tachybase/evaluators';
import { parse } from '@tachybase/utils';
@ -160,10 +162,14 @@ export async function send(this: CustomRequestPlugin, ctx: Context, next: Next)
);
try {
ctx.body = await axios(axiosRequestConfig).then((res) => {
this.logger.info(`custom-request:send:${filterByTk} success`);
return res.data;
});
const res = await axios({ ...axiosRequestConfig, responseType: 'stream' });
ctx.set('Content-Type', `${res.headers['content-type']}`);
ctx.set('Content-disposition', `${res.headers['content-disposition']}`);
this.logger.info(`custom-request:send:${filterByTk} success`);
const readable = res.data as http.IncomingMessage;
ctx.body = readable;
} catch (err) {
if (axios.isAxiosError(err)) {
ctx.status = err.response?.status || 500;

View File

@ -18,7 +18,6 @@ export const generate = async (ctx: Context) => {
},
appends: ['template'],
});
console.log(template.get());
const rawTemplate = template.get();
const templatePath = path.join(process.env.PWD, rawTemplate.template[0].get().url);
@ -27,18 +26,8 @@ export const generate = async (ctx: Context) => {
try {
// 生成定制的 docx 文件
const buffer = generateDocxFromTemplate(templatePath, data);
// 创建一个可读流
const readableStream = new Readable({
read(size) {
this.push(buffer);
this.push(null);
},
});
ctx.withoutDataWrapping = true;
ctx.set('Content-Type', 'application/octet-stream');
ctx.body = readableStream;
ctx.body = buffer;
} catch (error) {
console.error('Error:', error);
ctx.status = 500;
@ -66,7 +55,6 @@ export const getTags = async (ctx: Context) => {
// 获取所有占位符
const allTags = iModule.getAllTags();
console.log('All tags in the document:', allTags);
// 将 tags 转换为 JSON 格式的字符串
const tagsJson = JSON.stringify(allTags, null, 2);

View File

@ -16,6 +16,7 @@ export class PluginPrintTemplateServer extends Plugin {
getTags,
},
});
this.app.acl.allow('printTemplates', '*', 'public');
}
async install() {}

View File

@ -2454,6 +2454,9 @@ importers:
antd:
specifier: 5.19.1
version: 5.19.1(react-dom@18.3.1)(react@18.3.1)
file-saver:
specifier: ^2.0.5
version: 2.0.5
lodash:
specifier: ~4.17.21
version: 4.17.21