RekTec.Crm.DynamicForm

RekTec.Crm.DynamicForm

FormBuilder类

GetEditForm和SaveForm

起承转

钉钉销售信息编辑界面

/module/opportunity/OpportunityEdit.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import prompt from '../../mixins/prompt';
import FormScript from "./scripts/FormScript.js";
import { invokeHiddenApi } from "vue-xcrmsdk";
export default {
mixins: [prompt],
data() {
return {
isLoaded: false,
entityId: null,
formScript: new FormScript(),
formSchema: {},
formValues: {},
photoList: []
};
},
async mounted() {
this.entityId = this.$route.params.opportunityId || "";

await this.loadOpportunity();
},
methods: {
async loadOpportunity() {
rt.showLoadingToast(this.$_t("common.loading", "正在加载数据..."));
let params = {
opportunityId: this.entityId
};
invokeHiddenApi(
"new_opportunity",
"Opportunity/GetOpportunityInfo",
params
)
.then(res => {
this.formScript.$routeParams = this.$route.params;

this.formSchema = res.schema;
this.formScript.$schema = res.schema;
if (res.values) {
this.formValues = res.values;
this.formScript.$model = res.values;
}
if (this.entityId) {
this.formScript.entityId = this.entityId;
}
if (this.formScript.onLoad) {
this.formScript.onLoad();
}
this.isLoaded = true;
rt.hideLoadingToast();
})
.catch(e => {
rt.hideLoadingToast();
rt.showErrorToast(e.message);
});
},
async saveOpportunity() {
try {
let { result, errors } = await this.$refs.form.validator();

if (!result) {
rt.alert(errors.join("<br />"));
return;
}
rt.showLoadingToast(this.$_t("common.saving", "正在保存..."));
console.log(this.formValues)
let params = {
opportunityId: this.entityId,
formValues: this.formValues
};

if (this.$route.params.leadId) {
params.leadId = this.$route.params.leadId;
}
let res = await invokeHiddenApi(
"new_opportunity",
"Opportunity/SaveOpportunity",
params
);
//this.$bus.$emit("opportunity-updated");

rt.hideLoadingToast();
rt.showSuccessToast(this.$_t("common.saveSuccess", "保存成功"));
this.isSaved = true; // 用于未保存返回提示判断
this.$bus.$emit('opportunity-updated')
setTimeout(() => {
this.$router.back();
}, 1500);
} catch (e) {
rt.hideLoadingToast();
rt.showErrorToast(e.message);
}
}
}
};
CRM HiddenApi

RekTec.Crm.WorkFlow.Opportunity.Command.OpportunityCommand

GetOpportunityInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// 获取商机详细信息
/// </summary>
/// <param name="accountId"></param>
/// <returns></returns>
public DynamicEditForm GetOpportunityInfo(string entityId)
{
return FormBuilder.GetEditForm(OrganizationServiceAdmin, OrganizationService, "opportunity", entityId, LangCode, TimeZoneInfo);
}

/// <summary>
/// 保存销售信息
/// </summary>
/// <param name="model"></param>
public string SaveOpportunity(string opportunityId, Dictionary<string, object> formValues)
{
formValues.Remove("parentaccountid");
var primarycontactName = string.Empty;
var oppId = FormBuilder.SaveForm(OrganizationServiceAdmin, OrganizationService, "opportunity", opportunityId, formValues, LangCode);

return oppId;
}

SaveOpportunity

1
2
3
4
5
6
7
8
9
10
11
12
13

/// <summary>
/// 保存销售信息
/// </summary>
/// <param name="model"></param>
public string SaveOpportunity(string opportunityId, Dictionary<string, object> formValues)
{
formValues.Remove("parentaccountid");
var primarycontactName = string.Empty;
var oppId = FormBuilder.SaveForm(OrganizationServiceAdmin, OrganizationService, "opportunity", opportunityId, formValues, LangCode);

return oppId;
}
FormBuilder类

RekTec.Crm.DynamicForm.FormBuilder

GetReadonlyForm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// <summary>
/// 获取实体的可编辑表单,包括结构及数据
/// </summary>
/// <param name="organizationServiceAdmin"></param>
/// <param name="organizationService"></param>
/// <param name="entityName"></param>
/// <param name="entityId"></param>
/// <param name="langId"></param>
/// <returns></returns>
public static DynamicEditForm GetEditForm(IOrganizationService organizationServiceAdmin, IOrganizationService organizationService, string entityName, string entityId, int langId, TimeZoneInfo timeZoneInfo)
{
var formSchema = FormSchemaUtil.GetFormSchema(organizationServiceAdmin, entityName, langId);

var formValues = GetFormValues(organizationServiceAdmin, entityName, entityId, formSchema, timeZoneInfo);

var form = new DynamicEditForm
{
Schema = formSchema,
Values = formValues
};

return form;
}

调用示例

GetEditForm
返回结果
1
2
3
4
5

var paramList = {
opportunityId: "7C825824-BC69-EB11-8AC1-005056AF9937",
};
rtcrm.invokeHiddenApi("new_opportunity", "Opportunity/GetOpportunityInfo", paramList);

前后台关联
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//1. OpportunityDetail.vue 调用api 将值传入 rt-dynamic-form

<rt-dynamic-form ref="form" :script="formScript" :schema="formSchema" v-model="formValues"></rt-dynamic-form>

//2. DynamicForm.vue 将值遍历传入DynamicInput.vue

<rt-form label-position="top">
<div v-for="(section,sectionName) in schema" :key="sectionName" class="form-section">
<template v-if="section.attrs">
<dynamic-input
v-for="(attrOptions,attrName) in section.attrs"
:key="attrName"
:name="attrName"
v-model="inputValues[attrName]"
:options="attrOptions"
@custom-event="handleCustomEvent"
></dynamic-input>
</template>
</div>
<!-- {{inputValues}} -->
</rt-form>

//3. DynamicInput.vue 跟进api值渲染各个字段

<rt-text-input
v-if="options.type === 'text' && !options.helpButton"
v-show="options.visible"
:required="options.required"
:showLabel="options.showLabel"
:disabled="options.disabled"
v-model="inputValue"
:label="options.label"
:placeholder="options.placeholder"
/>
动态表单

GetEditForm方法中调用了FormSchemaUtil类中的GetFormSchema方法
GetFormSchema方法调用GetFormXmlString和ParseXmlToSchema方法

1
2
3
4
5
6
7
8
9
10
//GetEditForm()

var formSchema = FormSchemaUtil.GetFormSchema(organizationServiceAdmin, entityName, langId);

//FormSchemaUtil

var formXml = GetFormXmlString(organizationServiceAdmin, entityName);

var formSchema = ParseXmlToSchema(organizationServiceAdmin, entityName, formXml, langId);

new_systemform对应系统设置菜单中的动态表单实体,可以将系统中的实体信息以配置文件的形式存储用来在其他系统端口根据实体的相关信息动态的自动生成页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//GetFormXmlString()
/// <summary>
/// 加载实体对应的表单配置XML
/// </summary>
/// <param name="entityName"></param>
/// <returns></returns>
private static XmlDocument GetFormXmlString(IOrganizationService organizationServiceAdmin, string entityName)
{

var qe = new QueryExpression("new_systemform");
qe.Criteria.AddCondition("statecode", ConditionOperator.Equal, 0);
qe.Criteria.AddCondition("new_entityname", ConditionOperator.Equal, entityName);
qe.ColumnSet = new ColumnSet("new_formxml");

var ec = organizationServiceAdmin.RetrieveMultiple(qe);
if (ec == null || ec.Entities.Count == 0)
{
throw new Exception(string.Format("没有实体名为{0}的数据", entityName));
}

var entity = ec.Entities.First();
if (!entity.Contains("new_formxml"))
{
throw new Exception(string.Format("没有查询到对应的XML!实体名:【{0}】", entityName));
}

var xml = entity.GetAttributeValue<string>("new_formxml");

var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xml);

return xmlDoc;
}
```

> ParseXmlToSchema调用InputProperty类的LoadFromXml方法

```csharp
//ParseXmlToSchema

/// <summary>
/// 将XML转换成SCHEMA
/// </summary>
/// <param name="entityName"></param>
/// <param name="formXml"></param>
/// <returns></returns>
private static FormSchema ParseXmlToSchema(IOrganizationService organizationServiceAdmin, string entityName, XmlDocument formXml, int langId)
{
var schema = new FormSchema();

var sectionNodeList = formXml.SelectNodes("Form/Section");
if (sectionNodeList == null || sectionNodeList.Count == 0)
{
throw new Exception("未找到相应的配置结点,结点路径:Form/Section");
}

foreach (XmlNode sectionNode in sectionNodeList)
{
var sectionName = sectionNode.GetStringAttributeValue("name");
if (string.IsNullOrWhiteSpace(sectionName))
{
throw new Exception("Section结点的【name】属性值不能为空");
}

var sectionTitle = sectionNode.GetStringAttributeValue("title");
if (string.IsNullOrWhiteSpace(sectionTitle))
{
throw new Exception("Section结点的【title】属性值不能为空");
}

var fieldNodeList = sectionNode.SelectNodes("Field");
if (fieldNodeList == null || fieldNodeList.Count == 0)
{
throw new Exception($"Section结点下面未配置任何字段,SectionName:{sectionName}");
}

var sectionAttributes = new Dictionary<string, InputProperty.InputProperty>(fieldNodeList.Count);
foreach (XmlNode fieldNode in fieldNodeList)
{
var fieldName = fieldNode.GetStringAttributeValue("name");
if (string.IsNullOrWhiteSpace(fieldName))
{
throw new Exception($"Field结点的【name】属性值不能为空");
}

var fieldType = fieldNode.GetStringAttributeValue("type");
if (string.IsNullOrWhiteSpace(fieldName))
{
throw new Exception($"Field结点的【type】属性值不能为空");
}

var inputProperty = InputPropertyFactory.CreateInputProperty(organizationServiceAdmin, fieldType, langId);
inputProperty.LoadFromXml(entityName, fieldName, fieldNode);

sectionAttributes.Add(fieldName, inputProperty);
}

schema.Add(sectionName, new FormSectionProperty
{
Title = sectionTitle,
Attributes = sectionAttributes
});
}

return schema;
}

如果配置文件有值,则优先取配置文件的值,否则使用CRM中的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

public virtual void LoadFromXml(string entityName, string attrName, XmlNode xmlNode)
{
var entityMetadata = EntityMetadataHelper.GetEntityMetadata(this.OrganizationServiceAdmin, entityName, LangId);
if (entityMetadata == null)
{
throw new Exception($"entityMetadata is null,entityName:{entityName}");
}

AttributeMetadata attributeMetadata = null;
if (Type != AttrTypes.MultiLookup)
{
attributeMetadata = EntityMetadataHelper.GetAttributeMetadata(entityMetadata, attrName);
}

#region Label,如果配置文件有值,则优先取配置文件的值,否则使用CRM中的值
var label = PluginResourceHelper.GetResource(this.OrganizationServiceAdmin, LangId, $"{entityName}.{attrName}");
if (string.IsNullOrWhiteSpace(label))
{
label = attributeMetadata?.DisplayName?.GetUserLocalizedLabel(LangId);
}
if (string.IsNullOrWhiteSpace(label))
{
label = xmlNode.GetStringAttributeValue("label");
}
this.Label = label;
#endregion

#region Placeholder,先取翻译,没有就取配置文件,再没有就默认
var placeholder = PluginResourceHelper.GetResource(this.OrganizationServiceAdmin, LangId, $"{entityName}.{attrName}Placeholder"); //加了默认值后英文版显示:请输入Name
if (string.IsNullOrWhiteSpace(placeholder))
{
placeholder = xmlNode.GetStringAttributeValue("placeholder");
}
if (string.IsNullOrWhiteSpace(placeholder))
{
if (SelectInputTypes.Contains(this.Type))
{
placeholder = string.Format(PluginResourceHelper.GetResource(this.OrganizationServiceAdmin, LangId, "common.genericSelectPlaceholder", "请选择{0}"), this.Label); //$"请选择{this.Label}";
}
else
{
placeholder = string.Format(PluginResourceHelper.GetResource(this.OrganizationServiceAdmin, LangId, "common.genericInputPlaceholder", "请输入{0}"), this.Label);// $"请输入{this.Label}";
}
}
this.Placeholder = placeholder;
#endregion

this.Disabled = xmlNode.GetBooleanAttributeValue("disabled");
this.OnChange = xmlNode.GetStringAttributeValue("onchange");
this.Readonly = xmlNode.GetBooleanAttributeValue("readonly");
this.ShowLabel = xmlNode.GetBooleanAttributeValue("showlabel", true);
this.Visible = xmlNode.GetBooleanAttributeValue("visible", true);

//Required,如果配置文件有值,则优先取配置文件的值,否则使用CRM中的值
if (xmlNode.HasAttribute("required"))
{
this.Required = xmlNode.GetBooleanAttributeValue("required");
}
else
{
this.Required = attributeMetadata != null && attributeMetadata.IsRequired;
}
}

更改过动态表单以后需要回收crm站点程序池刷新缓存
CRMAppPool和CrmDeploymentServiceAppPool