刘光辉
8 小时以前 cf11e63b659afcc9792ab3e395f1b45d844431a8
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
<script setup lang="ts">
import { computed, onBeforeUnmount, ref } from 'vue';
 
import { fetchAnnotationCatalog } from './api/workflow-steps';
import { addRepeatPatternField, annotationAlias, annotationFields, annotationPlaceholder, annotationTag, displayFieldLabel, formAnnotationAlias, formAnnotationTag, removeRepeatPatternField, repeatGroups, repeatInsertSpec, repeatPatternUsesField, repeatPatternValidationError, stepKey, type AnnotationField, type FormField, type WorkflowStep } from './domain/annotation';
import { parsePluginContext, type QianpiyePluginContext } from './domain/context';
import { insertAnnotationControlCommand, parseInsertResult, stageFlowVersionPropertiesCommand } from './onlyoffice/commands';
import { bindPluginReady } from './onlyoffice/lifecycle';
 
const steps = ref<WorkflowStep[]>([]);
const formFields = ref<FormField[]>([]);
const selectedRepeat = ref<string>();
const repeatPatternDrafts = ref<Record<string, string>>({});
const selectedStep = ref<string>();
const loading = ref(true);
const error = ref('');
const versionWarning = ref('');
const insertingTag = ref('');
const groups = computed(() => repeatGroups(formFields.value));
const repeat = computed(() => groups.value.find((item) => item.key === selectedRepeat.value));
const repeatPattern = computed({
  get: () => selectedRepeat.value ? repeatPatternDrafts.value[selectedRepeat.value] || '' : '',
  set: (value: string) => {
    if (selectedRepeat.value) repeatPatternDrafts.value[selectedRepeat.value] = value;
  },
});
const repeatPatternError = computed(() => repeat.value && repeatPattern.value
  ? repeatPatternValidationError(repeat.value, repeatPattern.value)
  : '');
const canInsertRepeat = computed(() => Boolean(repeat.value && repeatPattern.value
  && !repeatPatternError.value && !insertingTag.value));
let context: QianpiyePluginContext | undefined;
const step = computed(() => steps.value.find((item) => stepKey(item) === selectedStep.value));
const fields = computed(() => annotationFields(step.value));
 
async function loadSteps(refresh = false) {
  loading.value = true;
  error.value = '';
  if (refresh) versionWarning.value = '';
  try {
    context = parsePluginContext(window.Asc?.plugin?.info?.options);
    if (!context.canAnnotate) {
      steps.value = [];
      selectedStep.value = undefined;
      error.value = '当前文档为只读,无法标注';
      return;
    }
    const catalog = await fetchAnnotationCatalog(context, refresh);
    if (refresh) await stageFlowVersionProperties(catalog.newFlowVersionId, catalog.revisionFlowVersionId);
    steps.value = catalog.workflowSteps;
    formFields.value = catalog.formFields.filter((field) => field.key.split('.').pop() !== 'biz_sign');
    selectedStep.value = steps.value[0] ? stepKey(steps.value[0]) : undefined;
    if (!refresh) void stageFlowVersionProperties(catalog.newFlowVersionId, catalog.revisionFlowVersionId)
      .catch(() => { versionWarning.value = '流程版本将在保存时写入,请先点击刷新'; });
  } catch (cause) {
    error.value = cause instanceof Error ? cause.message : '加载流程步骤失败';
  } finally {
    loading.value = false;
  }
}
 
function stageFlowVersionProperties(newFlowVersionId: string, revisionFlowVersionId: string): Promise<void> {
  window.Asc.scope.dmsQianpiyeFlowVersionProperties = { newFlowVersionId, revisionFlowVersionId };
  return new Promise((resolve, reject) => {
    window.Asc.plugin.callCommand(stageFlowVersionPropertiesCommand, false, true, (raw) => {
      const result = parseInsertResult(raw);
      if (result.ok) resolve();
      else reject(new Error(result.error || '保存签批页流程版本失败'));
    });
  });
}
function insert(tag: string, alias: string, placeholder = annotationPlaceholder(alias)) {
  if (!context?.canAnnotate || insertingTag.value) return;
  insertingTag.value = tag;
  error.value = '';
  window.Asc.scope.dmsQianpiyeInsert = {
    alias,
    placeholder,
    tag,
  };
  window.Asc.plugin.callCommand(insertAnnotationControlCommand, false, true, (raw) => {
    const result = parseInsertResult(raw);
    if (!result.ok) {
      error.value = result.error || '插入签批页标注失败';
    }
    insertingTag.value = '';
  });
}
function insertWorkflowField(field: AnnotationField) {
  if (!step.value) return;
  insert(annotationTag(step.value, field), annotationAlias(step.value, field));
}
function insertFormField(field: FormField) {
  insert(formAnnotationTag(field), formAnnotationAlias(field));
}
function toggleRepeatField(field: FormField) {
  repeatPattern.value = repeatPatternUsesField(repeatPattern.value, field)
    ? removeRepeatPatternField(repeatPattern.value, field)
    : addRepeatPatternField(repeatPattern.value, field);
}
function clearRepeatPattern() {
  repeatPattern.value = '';
}
function insertRepeat() {
  if (!repeat.value) return;
  try {
    const spec = repeatInsertSpec(repeat.value, repeatPattern.value);
    insert(spec.tag, spec.alias, spec.placeholder);
  } catch (cause) { error.value = cause instanceof Error ? cause.message : '子表格式无效'; }
}
const unbindPluginReady = bindPluginReady(window.Asc, () => {
  void loadSteps();
});
 
onBeforeUnmount(unbindPluginReady);
</script>
 
<template>
  <main class="panel">
    <a-alert v-if="error" type="error" show-icon :message="error" class="alert" />
    <a-alert v-if="versionWarning" type="warning" show-icon :message="versionWarning" class="alert" />
    <a-spin :spinning="loading">
      <section class="field-section">
        <div class="section-title"><h3>流程字段</h3><a-button size="small" :loading="loading" @click="loadSteps(true)">刷新</a-button></div>
        <a-select v-model:value="selectedStep" placeholder="选择流程步骤" class="full" :options="steps.map(item => ({ label: item.label, value: stepKey(item) }))" />
        <div v-if="step" class="field-list">
          <a-button v-for="field in fields" :key="field.key" block :loading="insertingTag === annotationTag(step, field)" :disabled="Boolean(insertingTag)" @click="insertWorkflowField(field)">{{ field.label }}</a-button>
        </div>
        <a-empty v-else-if="!loading" description="未找到可标注的发起或审核步骤" :image-style="{ height: '48px' }" />
      </section>
      <section class="field-section form-field-section">
        <h3>表单字段</h3>
        <div v-if="formFields.length" class="field-list form-field-list">
          <a-button v-for="field in formFields" :key="field.key" block :loading="insertingTag === formAnnotationTag(field)" :disabled="Boolean(insertingTag)" @click="insertFormField(field)">{{ displayFieldLabel(field.label) }}</a-button>
        </div>
        <a-empty v-else-if="!loading" class="form-field-empty" description="未找到适用流程的表单字段" :image-style="{ height: '48px' }" />
      </section>
      <section v-if="groups.length" class="field-section">
        <h3>子表逐行组合</h3>
        <a-select v-model:value="selectedRepeat" class="full" placeholder="选择子表" :options="groups.map(item => ({ label: item.label, value: item.key }))" />
        <template v-if="repeat">
          <div class="repeat-heading">
            <span class="repeat-label">选择字段</span>
            <a-button v-if="repeatPattern" type="link" size="small" :disabled="Boolean(insertingTag)" @click="clearRepeatPattern">清空</a-button>
          </div>
          <div class="repeat-fields">
            <a-button
              v-for="field in repeat.fields"
              :key="field.column"
              block
              :type="repeatPatternUsesField(repeatPattern, field) ? 'primary' : 'default'"
              :aria-pressed="repeatPatternUsesField(repeatPattern, field)"
              :disabled="Boolean(insertingTag)"
              @click="toggleRepeatField(field)">
              {{ displayFieldLabel(field.label.split('-').pop() || field.label) }}
            </a-button>
          </div>
          <label class="repeat-label repeat-format-label" for="repeat-pattern">每条记录格式</label>
          <a-input
            id="repeat-pattern"
            v-model:value="repeatPattern"
            class="repeat-pattern"
            :maxlength="2000"
            :disabled="Boolean(insertingTag)"
            placeholder="例如:{{部门}}({{份数}})" />
          <div class="repeat-feedback" aria-live="polite">
            <span v-if="repeatPatternError" class="repeat-error">{{ repeatPatternError }}</span>
            <span v-else-if="repeatPattern" class="repeat-preview">预览:{{ repeatPattern }}</span>
          </div>
          <a-button block type="primary" :loading="insertingTag === repeat.tag" :disabled="!canInsertRepeat" @click="insertRepeat">插入逐行组合</a-button>
        </template>
      </section>
    </a-spin>
  </main>
</template>