20260929172211
This commit is contained in:
1 parent
ebeeca45b6
commit
f337ff1621
16 files changed
+1997
-134
No files matched your search
+1567
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
@echo off
|
||||
set "JAVA_HOME=D:\devtool\jdk\jdk21"
|
||||
set "MAVEN_HOME=D:\devtool\maven"
|
||||
set "JAVA_HOME=D:\devtool\jdk\jdk-21"
|
||||
set "MAVEN_HOME=D:\devtool\maven\apache-maven-3.8.9"
|
||||
set "PATH=%JAVA_HOME%\bin;%MAVEN_HOME%\bin;%PATH%"
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,54 @@
|
||||
[INFO] Scanning for projects...
|
||||
[INFO]
|
||||
[INFO] -------------------------< cn.g3soft:fms-api >--------------------------
|
||||
[INFO] Building fms-api 0.0.1-SNAPSHOT
|
||||
[INFO] from pom.xml
|
||||
[INFO] --------------------------------[ jar ]---------------------------------
|
||||
[INFO]
|
||||
[INFO] >>> spring-boot-maven-plugin:4.1.0:run (default-cli) > test-compile @ fms-api >>>
|
||||
[INFO]
|
||||
[INFO] --- maven-resources-plugin:3.5.0:resources (default-resources) @ fms-api ---
|
||||
[INFO] Copying 1 resource from src\main\resources to target\classes
|
||||
[INFO] Copying 0 resource from src\main\resources to target\classes
|
||||
[INFO]
|
||||
[INFO] --- maven-compiler-plugin:3.15.0:compile (default-compile) @ fms-api ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- maven-resources-plugin:3.5.0:testResources (default-testResources) @ fms-api ---
|
||||
[INFO] skip non existing resourceDirectory D:\workspace\code\fms\fms-api\src\test\resources
|
||||
[INFO]
|
||||
[INFO] --- maven-compiler-plugin:3.15.0:testCompile (default-testCompile) @ fms-api ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] <<< spring-boot-maven-plugin:4.1.0:run (default-cli) < test-compile @ fms-api <<<
|
||||
[INFO]
|
||||
[INFO]
|
||||
[INFO] --- spring-boot-maven-plugin:4.1.0:run (default-cli) @ fms-api ---
|
||||
[INFO] Attaching agents: []
|
||||
|
||||
. ____ _ __ _ _
|
||||
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
|
||||
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
|
||||
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
||||
' |____| .__|_| |_|_| |_\__, | / / / /
|
||||
=========|_|==============|___/=/_/_/_/
|
||||
|
||||
:: Spring Boot :: (v4.1.0)
|
||||
|
||||
2026-09-29T08:30:17.346+08:00 INFO 12408 --- [fms-api] [ restartedMain] cn.g3soft.fmsapi.FmsApiApplication : Starting FmsApiApplication using Java 21 with PID 12408 (D:\workspace\code\fms\fms-api\target\classes started by Administrator in D:\workspace\code\fms\fms-api)
|
||||
2026-09-29T08:30:17.356+08:00 INFO 12408 --- [fms-api] [ restartedMain] cn.g3soft.fmsapi.FmsApiApplication : No active profile set, falling back to 1 default profile: "default"
|
||||
2026-09-29T08:30:17.440+08:00 INFO 12408 --- [fms-api] [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
|
||||
2026-09-29T08:30:17.440+08:00 INFO 12408 --- [fms-api] [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
|
||||
2026-09-29T08:30:19.863+08:00 INFO 12408 --- [fms-api] [ restartedMain] o.s.boot.tomcat.TomcatWebServer : Tomcat initialized with port 8088 (http)
|
||||
2026-09-29T08:30:19.910+08:00 INFO 12408 --- [fms-api] [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
|
||||
2026-09-29T08:30:19.911+08:00 INFO 12408 --- [fms-api] [ restartedMain] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/11.0.22]
|
||||
2026-09-29T08:30:20.005+08:00 INFO 12408 --- [fms-api] [ restartedMain] b.w.c.s.WebApplicationContextInitializer : Root WebApplicationContext: initialization completed in 2562 ms
|
||||
2026-09-29T08:30:20.821+08:00 INFO 12408 --- [fms-api] [ restartedMain] c.g3soft.fmsapi.utils.IpRegionSearcher : ip2region Searcher 初始化完成,数据大小 11114380 字节
|
||||
2026-09-29T08:30:21.243+08:00 INFO 12408 --- [fms-api] [ restartedMain] c.g.f.database.OrgDatabaseConfigWatcher : 已启动机构配置目录监听: D:\workspace\code\fms\fms-api\config\dbconfigs
|
||||
2026-09-29T08:30:21.781+08:00 INFO 12408 --- [fms-api] [ restartedMain] o.s.boot.tomcat.TomcatWebServer : Tomcat started on port 8088 (http) with context path '/api'
|
||||
2026-09-29T08:30:21.791+08:00 INFO 12408 --- [fms-api] [ restartedMain] cn.g3soft.fmsapi.FmsApiApplication : Started FmsApiApplication in 5.29 seconds (process running for 7.111)
|
||||
2026-09-29T08:39:25.507+08:00 INFO 12408 --- [fms-api] [nio-8088-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/api] : Initializing Spring DispatcherServlet 'dispatcherServlet'
|
||||
2026-09-29T08:39:25.508+08:00 INFO 12408 --- [fms-api] [nio-8088-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
|
||||
2026-09-29T08:39:25.511+08:00 INFO 12408 --- [fms-api] [nio-8088-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
|
||||
2026-09-29T08:39:25.854+08:00 WARN 12408 --- [fms-api] [nio-8088-exec-1] com.alibaba.druid.pool.DruidDataSource : removeAbandoned is true, not use in production.
|
||||
2026-09-29T08:39:26.354+08:00 INFO 12408 --- [fms-api] [nio-8088-exec-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-1,G3HD} inited
|
||||
@@ -1602,6 +1602,7 @@ defineExpose({
|
||||
v-if="views.length || keywordConfig || quickItems.length"
|
||||
class="fms-module-card query-card"
|
||||
>
|
||||
<!-- 页签是「在哪个队列里干活」的上级入口(分组可多行),独占一行 -->
|
||||
<FmsWorkQueueTabs
|
||||
:views="views"
|
||||
:active-codes="viewCodes"
|
||||
@@ -1609,23 +1610,28 @@ defineExpose({
|
||||
@update:active-codes="changeView"
|
||||
@load-counts="requestTabCounts"
|
||||
/>
|
||||
<FmsKeywordSearch
|
||||
v-if="keywordConfig"
|
||||
v-model="keyword"
|
||||
:placeholder="keywordPlaceholder"
|
||||
@search="searchKeyword"
|
||||
/>
|
||||
<FmsQuickFilters
|
||||
v-if="quickItems.length"
|
||||
v-model="quickValues"
|
||||
:items="quickItems"
|
||||
:loading="loading"
|
||||
:advanced-count="appliedFilterCount"
|
||||
:advanced-enabled="moreFilters.length > 0"
|
||||
@search="search"
|
||||
@reset="resetSearch"
|
||||
@open-advanced="openFilters"
|
||||
/>
|
||||
<!-- 条件行:关键词定宽最左,快捷条件横排居中,查询/重置/更多筛选贴右端 -->
|
||||
<div v-if="keywordConfig || quickItems.length" class="query-row">
|
||||
<FmsKeywordSearch
|
||||
v-if="keywordConfig"
|
||||
v-model="keyword"
|
||||
class="query-row__keyword"
|
||||
:placeholder="keywordPlaceholder"
|
||||
@search="searchKeyword"
|
||||
/>
|
||||
<FmsQuickFilters
|
||||
v-if="quickItems.length"
|
||||
class="query-row__quick"
|
||||
v-model="quickValues"
|
||||
:items="quickItems"
|
||||
:loading="loading"
|
||||
:advanced-count="appliedFilterCount"
|
||||
:advanced-enabled="moreFilters.length > 0"
|
||||
@search="search"
|
||||
@reset="resetSearch"
|
||||
@open-advanced="openFilters"
|
||||
/>
|
||||
</div>
|
||||
<FmsActiveFilterPills
|
||||
:pills="activePills"
|
||||
@remove="removePill"
|
||||
@@ -1806,7 +1812,7 @@ defineExpose({
|
||||
box-shadow: 0 2px 6px rgb(31 35 41 / 4%);
|
||||
}
|
||||
|
||||
/* 查询卡:页签 / 关键词 / 快捷筛选 / 条件回显纵向排列,卡间 8px 灰缝由父级 gap 提供 */
|
||||
/* 查询卡:页签 / 条件行 / 条件回显纵向排列,卡间 8px 灰缝由父级 gap 提供 */
|
||||
.query-card {
|
||||
display: flex;
|
||||
flex: none;
|
||||
@@ -1815,6 +1821,27 @@ defineExpose({
|
||||
padding: 10px 16px 12px;
|
||||
}
|
||||
|
||||
/* 条件行:关键词 + 快捷筛选(条件横排 + 动作簇贴右端)同排,页签因此不必跟着条件一起换行。
|
||||
末行对齐:中文横排下关键词与条件同为一行高,末行对齐等于顶对齐;
|
||||
非中文竖排时条件是「label 在上、控件在下」,末行对齐才能让关键词框与控件行齐平 */
|
||||
.query-row {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: flex-end;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 关键词定宽:不随条件数伸缩,位置跨模块、跨屏宽一致 */
|
||||
.query-row__keyword {
|
||||
flex: none;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.query-row__quick {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 表格卡:工具条 + 表格 + 分页,撑满剩余高度,内部滚动 */
|
||||
.table-card {
|
||||
display: flex;
|
||||
|
||||
@@ -100,55 +100,67 @@ function isDateBeforeLimit(valueToCompare) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 日期预设:语义区间(今天/本周/本月/近三个月)优先,选「自定义」才落到两端日期 -->
|
||||
<FmsField
|
||||
v-if="showDatePreset"
|
||||
v-model="datePreset"
|
||||
class="fms-query-control"
|
||||
type="select"
|
||||
:disabled="disabled"
|
||||
:control-props="{ options: datePresetOptions, filterable: false }"
|
||||
/>
|
||||
<!-- 日期区间:预设下拉与「自定义」的起止两端并排一行。
|
||||
选语义区间时只显示下拉(下拉铺满整格,不留空档),选「自定义」时下拉收窄、两端出现 ——
|
||||
两者不再上下堆叠 -->
|
||||
<div
|
||||
v-if="isRange && (!showDatePreset || datePreset === 'custom')"
|
||||
class="fms-query-control fms-query-control__range"
|
||||
:class="{ 'fms-query-control__range--compact': compact }"
|
||||
v-if="showDatePreset || isRange"
|
||||
class="fms-query-control fms-query-control__row"
|
||||
:class="{ 'fms-query-control__row--preset': showDatePreset }"
|
||||
>
|
||||
<!-- 日期区间:两端是两个独立控件,各自只受另一端约束;不接 enter —— Enter 已被日期面板用于开合 -->
|
||||
<template v-if="isDate">
|
||||
<FmsField
|
||||
v-model="startValue"
|
||||
:type="isDateTime ? 'datetime' : 'date'"
|
||||
:disabled="disabled"
|
||||
:control-props="{
|
||||
...dateFormatProps,
|
||||
timeDefault: 'start',
|
||||
disabledDate: isDateAfterLimit,
|
||||
}"
|
||||
/>
|
||||
<span class="fms-query-control__separator">-</span>
|
||||
<FmsField
|
||||
v-model="endValue"
|
||||
:type="isDateTime ? 'datetime' : 'date'"
|
||||
:disabled="disabled"
|
||||
:control-props="{ ...dateFormatProps, timeDefault: 'end', disabledDate: isDateBeforeLimit }"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<FmsField
|
||||
v-model="startValue"
|
||||
:type="isNumeric ? 'number' : 'input'"
|
||||
:disabled="disabled"
|
||||
@keydown="onEnter"
|
||||
/>
|
||||
<span class="fms-query-control__separator">-</span>
|
||||
<FmsField
|
||||
v-model="endValue"
|
||||
:type="isNumeric ? 'number' : 'input'"
|
||||
:disabled="disabled"
|
||||
@keydown="onEnter"
|
||||
/>
|
||||
</template>
|
||||
<FmsField
|
||||
v-if="showDatePreset"
|
||||
v-model="datePreset"
|
||||
class="fms-query-control__preset"
|
||||
type="select"
|
||||
:disabled="disabled"
|
||||
:control-props="{ options: datePresetOptions, filterable: false }"
|
||||
/>
|
||||
<div
|
||||
v-if="isRange && (!showDatePreset || datePreset === 'custom')"
|
||||
class="fms-query-control__range"
|
||||
:class="{ 'fms-query-control__range--compact': compact }"
|
||||
>
|
||||
<!-- 日期区间:两端是两个独立控件,各自只受另一端约束;不接 enter —— Enter 已被日期面板用于开合 -->
|
||||
<template v-if="isDate">
|
||||
<FmsField
|
||||
v-model="startValue"
|
||||
:type="isDateTime ? 'datetime' : 'date'"
|
||||
:disabled="disabled"
|
||||
:control-props="{
|
||||
...dateFormatProps,
|
||||
timeDefault: 'start',
|
||||
disabledDate: isDateAfterLimit,
|
||||
}"
|
||||
/>
|
||||
<span class="fms-query-control__separator">-</span>
|
||||
<FmsField
|
||||
v-model="endValue"
|
||||
:type="isDateTime ? 'datetime' : 'date'"
|
||||
:disabled="disabled"
|
||||
:control-props="{
|
||||
...dateFormatProps,
|
||||
timeDefault: 'end',
|
||||
disabledDate: isDateBeforeLimit,
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<FmsField
|
||||
v-model="startValue"
|
||||
:type="isNumeric ? 'number' : 'input'"
|
||||
:disabled="disabled"
|
||||
@keydown="onEnter"
|
||||
/>
|
||||
<span class="fms-query-control__separator">-</span>
|
||||
<FmsField
|
||||
v-model="endValue"
|
||||
:type="isNumeric ? 'number' : 'input'"
|
||||
:disabled="disabled"
|
||||
@keydown="onEnter"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- checkbox 转三态下拉(全部/是/否):选项与判定逻辑留在本组件,渲染交给 FmsField -->
|
||||
@@ -192,6 +204,27 @@ function isDateBeforeLimit(valueToCompare) {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 条件控件行:并排的预设下拉 + 起止两端(普通区间只有一个子项,等同原来的整格控件) */
|
||||
.fms-query-control__row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 预设下拉平时铺满整格(选语义区间时没有两端,不留空档);
|
||||
选「自定义」后两端占 70%,下拉收缩到只放得下预设文案。
|
||||
两个子项都经 FmsField 渲染(FmsField 自身不产生元素),用 :deep 命中才稳 */
|
||||
.fms-query-control__row--preset :deep(.fms-query-control__preset) {
|
||||
flex: 1 1 96px;
|
||||
min-width: 96px;
|
||||
}
|
||||
|
||||
.fms-query-control__row--preset :deep(.fms-query-control__range) {
|
||||
flex: 0 1 70%;
|
||||
}
|
||||
|
||||
.fms-query-control__range {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
|
||||
@@ -10,11 +10,19 @@ import { computed } from 'vue'
|
||||
import { ChevronDown, Filter, Search } from '@lucide/vue'
|
||||
import { Button, Form, FormItem } from '@/components/ui'
|
||||
import FmsQueryControl from './FmsQueryControl.vue'
|
||||
import { queryItemLabel } from '@/components/fms-module-common/queryUtils'
|
||||
import {
|
||||
isDateQuery,
|
||||
isRangeQuery,
|
||||
queryItemLabel,
|
||||
} from '@/components/fms-module-common/queryUtils'
|
||||
import { useI18nStore } from '@/stores/i18n'
|
||||
|
||||
/** 条件列宽上限:4 列整行 = 4×288 + 3×16(列间距)= 1200px,不含右侧操作区 */
|
||||
const GRID_MAX_COLUMN_WIDTH = 288
|
||||
/** 单列列宽(px):普通单值控件(input / select / 三态 checkbox)够用即可,不必占满整行 */
|
||||
const COLUMN_WIDTH = 200
|
||||
/** 宽控件占的列数:区间要放下两端,日期区间还要并排放预设下拉,多选的标签也占宽 */
|
||||
const WIDE_COLUMN_SPAN = 2
|
||||
/** 一行最多铺的列数(1 列 = COLUMN_WIDTH),超出换行;与旧系统「定宽列 + 宽控件跨两列」一致 */
|
||||
const MAX_COLUMNS = 4
|
||||
|
||||
const props = defineProps({
|
||||
// 快捷筛选条件项(已按配置顺序排序、已按字段权限裁剪)
|
||||
@@ -35,10 +43,27 @@ const values = computed({
|
||||
set: (next) => emit('update:modelValue', next),
|
||||
})
|
||||
|
||||
/** 网格列数按实际条件数收缩(最多 4 列):只有一两个条件时不应铺满整行 */
|
||||
const gridStyle = computed(() => ({
|
||||
gridTemplateColumns: `repeat(${Math.min(Math.max(props.items.length, 1), 4)}, minmax(0, ${GRID_MAX_COLUMN_WIDTH}px))`,
|
||||
}))
|
||||
/** 控件横向需求大的条件:区间(两端)/ 日期(预设下拉 + 两端)/ 多选(标签占宽) */
|
||||
function needsWideColumn(item) {
|
||||
const multiValue = ['in', 'not_in'].includes(item.config.b_operator)
|
||||
return isRangeQuery(item) || isDateQuery(item) || multiValue
|
||||
}
|
||||
|
||||
/** 栅格列数按各条件占用的列数求和后收缩(最多 MAX_COLUMNS 列):
|
||||
* 只有一两个普通条件时不该铺满整行,宽控件也不会因列数不够被挤到下一行 */
|
||||
const gridStyle = computed(() => {
|
||||
const units = (props.items || []).reduce(
|
||||
(total, item) => total + (needsWideColumn(item) ? WIDE_COLUMN_SPAN : 1),
|
||||
0,
|
||||
)
|
||||
const columns = Math.min(Math.max(units, 1), MAX_COLUMNS)
|
||||
return { gridTemplateColumns: `repeat(${columns}, minmax(0, ${COLUMN_WIDTH}px))` }
|
||||
})
|
||||
|
||||
/** 宽控件跨两列:跨列写在 FormItem 上,栅格才给它两列的位置 */
|
||||
function columnStyleOf(item) {
|
||||
return needsWideColumn(item) ? { gridColumn: `span ${WIDE_COLUMN_SPAN}` } : undefined
|
||||
}
|
||||
|
||||
function labelOf(item) {
|
||||
return item.label || queryItemLabel(item, t)
|
||||
@@ -54,25 +79,32 @@ function submit() {
|
||||
|
||||
<template>
|
||||
<div class="fms-quick-filters">
|
||||
<div class="quick-grid" :style="gridStyle">
|
||||
<Form :model="values" label-width="72px" label-align="right">
|
||||
<FormItem
|
||||
v-for="item in items"
|
||||
:key="item.field.b_field"
|
||||
class="quick-field"
|
||||
:name="item.field.b_field"
|
||||
:label="labelOf(item)"
|
||||
>
|
||||
<FmsQueryControl
|
||||
:model-value="values[item.field.b_field]"
|
||||
:item="item"
|
||||
date-presets
|
||||
@update:model-value="setValue(item.field.b_field, $event)"
|
||||
@enter="submit"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
<!-- 栅格落在 Form 自身:FormItem 是它的直接子元素,条件才能横向铺开。
|
||||
若再套一层栅格容器包住 Form,整张表单只是该容器的唯一子项,FormItem 会退回块级纵向堆叠 -->
|
||||
<Form
|
||||
class="quick-grid"
|
||||
:style="gridStyle"
|
||||
:model="values"
|
||||
label-width="72px"
|
||||
label-align="right"
|
||||
>
|
||||
<FormItem
|
||||
v-for="item in items"
|
||||
:key="item.field.b_field"
|
||||
class="quick-field"
|
||||
:style="columnStyleOf(item)"
|
||||
:name="item.field.b_field"
|
||||
:label="labelOf(item)"
|
||||
>
|
||||
<FmsQueryControl
|
||||
:model-value="values[item.field.b_field]"
|
||||
:item="item"
|
||||
date-presets
|
||||
@update:model-value="setValue(item.field.b_field, $event)"
|
||||
@enter="submit"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<div class="quick-actions">
|
||||
<Button type="primary" :loading="loading" @click="submit">
|
||||
@@ -97,10 +129,12 @@ function submit() {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 末行对齐:中文横排(label 在左)下条件与动作簇同为一行高,末行对齐等于顶对齐;
|
||||
非中文竖排(label 在上)时动作簇才会落到控件那一行的底边,不与 label 平齐 */
|
||||
.fms-quick-filters {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: flex-start;
|
||||
align-items: flex-end;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
@@ -109,6 +143,11 @@ function submit() {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
gap: 12px 16px;
|
||||
|
||||
/* 行距只由栅格的 row-gap 提供,取消 FormItem 自带的下间距 */
|
||||
:deep(.fms-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.quick-field) {
|
||||
|
||||
@@ -23,7 +23,8 @@ const props = defineProps({
|
||||
// 是否在非横向虚拟滚动时自动铺满容器;默认按列配置宽度展示。
|
||||
autoFitWidth: { type: Boolean, default: false },
|
||||
// 列宽拖拽默认开启;false=关闭;{ disabled: (col) => boolean }=按列禁用
|
||||
// (铺满容器的弹性列应禁用拖拽,见库文档「列宽铺满容器hack方式」)
|
||||
// (铺满容器的弹性列应禁用拖拽,见库文档「列宽铺满容器hack方式」)。
|
||||
// 列上显式写了 resizable: false 的(操作列、展开列)一律不可拖,无需再传 disabled。
|
||||
colResizable: { type: [Boolean, Object], default: true },
|
||||
// 边框自绘:true=外框圆角描边+行分隔线(默认);false=无外框仅行分隔线(嵌入卡片场景)。
|
||||
// stk-table 库内边框恒为 false,不用其渐变边框。
|
||||
@@ -86,7 +87,24 @@ const tableTheme = computed(() => props.theme ?? (appStore.darkMode ? 'dark' : '
|
||||
// 直接关闭这两个功能。声明了 update:columns emit 后监听器不会进 attrs,
|
||||
// 需从 vnode.props 探测;绑定关系在模板编写时确定,运行期不会变化,无需响应式。
|
||||
const columnsBound = 'onUpdate:columns' in (getCurrentInstance()?.vnode.props ?? {})
|
||||
const effectiveColResizable = columnsBound && !props.autoFitWidth ? props.colResizable : false
|
||||
|
||||
// 列宽拖拽开关:库只提供「全局开关 + disabled(col) 回调」这一种按列控制方式,
|
||||
// 没有按列的 resizable,所以开启时统一转成回调形式,顺带落实列上显式声明的
|
||||
// resizable: false —— 操作列 / 展开列的宽度由代码算(拖了不落库、列重建即丢)。
|
||||
// 必须按列禁掉操作列:它 fixed: 'right' 且左边缘压在最后一列上,库对右侧固定列的
|
||||
// 「左侧把手」拖的是这一列自己,用户拖那根分割线就会改到操作列宽度(拖左列、动操作列)。
|
||||
const effectiveColResizable = (() => {
|
||||
if (!columnsBound || props.autoFitWidth || !props.colResizable) return false
|
||||
const configured = props.colResizable === true ? {} : props.colResizable
|
||||
return {
|
||||
disabled: (column) => {
|
||||
if (column.resizable === false) return true
|
||||
return typeof configured.disabled === 'function'
|
||||
? Boolean(configured.disabled(column))
|
||||
: Boolean(configured.disabled)
|
||||
},
|
||||
}
|
||||
})()
|
||||
|
||||
// Tab 面板从隐藏恢复时,等到容器拥有有效尺寸再刷新虚拟滚动布局。
|
||||
// rAF 将同一帧内的多次 ResizeObserver 通知合并,避免重复重排。
|
||||
@@ -733,7 +751,11 @@ defineExpose({
|
||||
.fms-table__inner.stk-table.bordered-v {
|
||||
th:last-child,
|
||||
td:last-child {
|
||||
background-image: none;
|
||||
/* 只抹掉最右一列的外侧右边线(外框由 .fms-table--bordered 画)。
|
||||
不能整体清空 background-image:库会给「被前面内容穿透遮挡」的右固定列补
|
||||
fixed-cell--border-left,并用 --bg-border-left 画它左边的分隔线;整份清空会
|
||||
把这根线一起干掉,表现为操作列压住内容却没有分割线。 */
|
||||
--bg-border-right: linear-gradient(transparent, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -844,9 +844,10 @@ function openPreview() {
|
||||
</Modal>
|
||||
|
||||
<!-- 预览:上方复刻列表页查询卡(可交互),下方给出生成的查询条件 -->
|
||||
<Modal v-model:open="previewOpen" title="查询预览" width="920px" :footer="false">
|
||||
<Modal v-model:open="previewOpen" title="查询预览" width="1180px" :footer="false">
|
||||
<div class="mm-preview">
|
||||
<div v-if="previewHasEntry" class="mm-preview__card">
|
||||
<!-- 与列表页同排布:页签独占一行,关键词与快捷筛选同行 -->
|
||||
<FmsWorkQueueTabs
|
||||
v-if="previewData.views.length"
|
||||
:views="previewData.views"
|
||||
@@ -855,22 +856,29 @@ function openPreview() {
|
||||
@update:active-codes="previewViewCodes = $event"
|
||||
@load-counts="() => {}"
|
||||
/>
|
||||
<FmsKeywordSearch
|
||||
v-if="previewData.keywordSearch"
|
||||
v-model="previewKeyword"
|
||||
:placeholder="previewKeywordPlaceholder"
|
||||
@search="() => {}"
|
||||
/>
|
||||
<FmsQuickFilters
|
||||
v-if="previewData.quickFilters.length"
|
||||
v-model="previewQuickValues"
|
||||
:items="previewData.quickFilters"
|
||||
:advanced-count="previewFilterCount"
|
||||
:advanced-enabled="previewMoreFilters.length > 0"
|
||||
@search="() => {}"
|
||||
@reset="resetPreviewQuick"
|
||||
@open-advanced="previewFilterOpen = true"
|
||||
/>
|
||||
<div
|
||||
v-if="previewData.keywordSearch || previewData.quickFilters.length"
|
||||
class="mm-preview__row"
|
||||
>
|
||||
<FmsKeywordSearch
|
||||
v-if="previewData.keywordSearch"
|
||||
v-model="previewKeyword"
|
||||
class="mm-preview__keyword"
|
||||
:placeholder="previewKeywordPlaceholder"
|
||||
@search="() => {}"
|
||||
/>
|
||||
<FmsQuickFilters
|
||||
v-if="previewData.quickFilters.length"
|
||||
class="mm-preview__quick"
|
||||
v-model="previewQuickValues"
|
||||
:items="previewData.quickFilters"
|
||||
:advanced-count="previewFilterCount"
|
||||
:advanced-enabled="previewMoreFilters.length > 0"
|
||||
@search="() => {}"
|
||||
@reset="resetPreviewQuick"
|
||||
@open-advanced="previewFilterOpen = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="mm-preview__empty">当前配置没有产生任何查询入口</p>
|
||||
|
||||
@@ -1157,6 +1165,24 @@ function openPreview() {
|
||||
background: var(--fms-surface);
|
||||
}
|
||||
|
||||
/* 条件行:与列表页查询卡同排布(关键词定宽最左,快捷筛选占满其余,末行对齐) */
|
||||
.mm-preview__row {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: flex-end;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.mm-preview__keyword {
|
||||
flex: none;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.mm-preview__quick {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mm-preview__empty {
|
||||
margin: 0;
|
||||
color: var(--fms-text-secondary);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Ban, Copy, Eye, Layers, Plus, Send, SquarePen, Trash2 } from '@lucide/vue'
|
||||
import { CircleQuestionMark, Copy, Plus, Trash2 } from '@lucide/vue'
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
Modal,
|
||||
Select,
|
||||
Tabs,
|
||||
Tooltip,
|
||||
} from '@/components/ui'
|
||||
import { FmsModuleListPage } from '@/components/fms-module-list'
|
||||
import { loadDataApi } from '@/services/api'
|
||||
@@ -95,10 +96,11 @@ function openDesign(definitionId) {
|
||||
router.push({ name: 'system-workflow-design', query: { id: definitionId } })
|
||||
}
|
||||
|
||||
/* 操作列只出文字,不带图标:图标会把按钮撑宽、超出操作列的预留宽度 */
|
||||
const definitionRowActions = computed(() => [
|
||||
{ key: 'design', label: '设计', icon: SquarePen, onClick: (row) => openDesign(row.b_id) },
|
||||
{ key: 'versions', label: '版本', icon: Layers, onClick: (row) => openVersionDrawer(row) },
|
||||
{ key: 'delete', label: '删除', icon: Trash2, danger: true, onClick: (row) => handleDeleteDefinition(row) },
|
||||
{ key: 'design', label: '设计', onClick: (row) => openDesign(row.b_id) },
|
||||
{ key: 'versions', label: '版本', onClick: (row) => openVersionDrawer(row) },
|
||||
{ key: 'delete', label: '删除', danger: true, onClick: (row) => handleDeleteDefinition(row) },
|
||||
])
|
||||
|
||||
function handleDeleteDefinition(row) {
|
||||
@@ -239,18 +241,16 @@ function handleRetireVersion(row) {
|
||||
}
|
||||
|
||||
const versionRowActions = computed(() => [
|
||||
{ key: 'design', label: '设计', icon: SquarePen, onClick: (row) => openDesign(row.b_definition_id) },
|
||||
{ key: 'design', label: '设计', onClick: (row) => openDesign(row.b_definition_id) },
|
||||
{
|
||||
key: 'publish',
|
||||
label: '发布',
|
||||
icon: Send,
|
||||
visible: (row) => row.b_status === 'draft',
|
||||
onClick: (row) => handlePublishVersion(row),
|
||||
},
|
||||
{
|
||||
key: 'retire',
|
||||
label: '停用',
|
||||
icon: Ban,
|
||||
danger: true,
|
||||
visible: (row) => row.b_status === 'published',
|
||||
onClick: (row) => handleRetireVersion(row),
|
||||
@@ -414,8 +414,8 @@ function handleBatchDeleteBindings() {
|
||||
}
|
||||
|
||||
const bindingRowActions = computed(() => [
|
||||
{ key: 'edit', label: '编辑', icon: SquarePen, onClick: (row) => openBindingEdit(row) },
|
||||
{ key: 'delete', label: '删除', icon: Trash2, danger: true, onClick: (row) => handleDeleteBinding(row) },
|
||||
{ key: 'edit', label: '编辑', onClick: (row) => openBindingEdit(row) },
|
||||
{ key: 'delete', label: '删除', danger: true, onClick: (row) => handleDeleteBinding(row) },
|
||||
])
|
||||
|
||||
/* ============================================================================
|
||||
@@ -432,7 +432,7 @@ function openInstanceDetail(row) {
|
||||
}
|
||||
|
||||
const instanceRowActions = computed(() => [
|
||||
{ key: 'view', label: '查看', icon: Eye, onClick: (row) => openInstanceDetail(row) },
|
||||
{ key: 'view', label: '查看', onClick: (row) => openInstanceDetail(row) },
|
||||
])
|
||||
</script>
|
||||
|
||||
@@ -511,7 +511,25 @@ const instanceRowActions = computed(() => [
|
||||
<FormItem name="moduleId" label="业务模块" required :span="1">
|
||||
<Select v-model="bindingForm.moduleId" :options="moduleOptions" placeholder="选择要接入审批的模块" />
|
||||
</FormItem>
|
||||
<FormItem name="event" label="业务事件" required :span="1">
|
||||
<!-- 业务事件决定「这次提交走哪条流程」,用 ? 提示解释三种取值,避免和流程内的条件分支混淆 -->
|
||||
<FormItem name="event" required :span="1">
|
||||
<template #label>
|
||||
<span class="wf-binding-event-label">
|
||||
业务事件
|
||||
<Tooltip placement="top">
|
||||
<template #content>
|
||||
<div class="wf-binding-event-tip">
|
||||
<p class="wf-binding-event-tip__title">业务事件决定这次提交走哪条流程:</p>
|
||||
<p><b>送审(submit)</b>:单据第一次提交审批;被驳回结束(作废)后重新发起也走这里。</p>
|
||||
<p><b>重新提交(resubmit)</b>:单据被退回修改后,发起人改完在原单上再次提交。</p>
|
||||
<p><b>变更(change)</b>:单据已审批通过后发生业务变更(如改金额)需再次审批;多数模块用不到。</p>
|
||||
<p class="wf-binding-event-tip__note">一次提交只会命中一条绑定:按「业务模块 + 业务事件 + 条件」匹配,多条命中时取优先级最小的一条。</p>
|
||||
</div>
|
||||
</template>
|
||||
<CircleQuestionMark class="wf-binding-event-help" :size="14" aria-label="业务事件说明" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<Select v-model="bindingForm.event" :options="EVENT_OPTIONS" />
|
||||
</FormItem>
|
||||
<FormItem name="versionId" label="流程版本" required :span="1">
|
||||
@@ -566,8 +584,15 @@ const instanceRowActions = computed(() => [
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 页签栏做成白卡:页面画布是灰底,列表内容是白卡,页签裸在灰底上会显得突兀。
|
||||
左右 8px 内边距 + 标签自身 8px = 下方卡片内容的 16px,保证首项文字左对齐 */
|
||||
.wf-page__nav.tabs {
|
||||
flex: none;
|
||||
padding: 0 8px;
|
||||
margin-bottom: 8px;
|
||||
background: var(--fms-card, #fff);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 6px rgb(31 35 41 / 4%);
|
||||
}
|
||||
|
||||
/* 各页签的列表铺满剩余高度:v-show 切换时只换显示,列表状态与滚动位置都保留 */
|
||||
@@ -588,4 +613,53 @@ const instanceRowActions = computed(() => [
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 「业务事件」标签上的 ? 图标:纯提示,不参与表单校验 */
|
||||
.wf-binding-event-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wf-binding-event-help {
|
||||
/* label 宽固定,图标作为 flex 项会被压缩导致部分被折叠,这里禁止收缩 */
|
||||
flex: none;
|
||||
color: var(--fms-text-secondary);
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/* label 文本区默认 overflow: hidden(超长省略号),会把超出固定 label 宽的 ? 图标裁掉 */
|
||||
.fms-form-item[data-field='event'] :deep(.fms-form-label-text) {
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* ---- 业务事件提示浮层排版 ----
|
||||
浮层经 Teleport 渲染到 body,scoped 样式命中不到,故用非 scoped 类;
|
||||
同时清掉 <p> 浏览器默认 margin,否则多行说明会把内容块撑得虚高。 */
|
||||
.wf-binding-event-tip {
|
||||
width: 320px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.wf-binding-event-tip p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wf-binding-event-tip p + p {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.wf-binding-event-tip__title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wf-binding-event-tip__note {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--fms-border-soft);
|
||||
color: var(--fms-text-secondary);
|
||||
}
|
||||
</style>
|
||||
@@ -181,7 +181,12 @@ describe('fms-table 虚拟滚动验证', () => {
|
||||
await nextTick()
|
||||
await new Promise((r) => setTimeout(r, 100))
|
||||
const stkTable = wrapper.findComponent(StkTable)
|
||||
expect(stkTable.props('colResizable')).toBe(true)
|
||||
// 开启时统一传 { disabled } 回调(库只支持这种按列控制方式):普通列可拖,
|
||||
// 列上显式写 resizable: false 的(操作列 / 展开列)不拖
|
||||
const colResizable = stkTable.props('colResizable')
|
||||
expect(colResizable).toMatchObject({ disabled: expect.any(Function) })
|
||||
expect(colResizable.disabled({ dataIndex: 'c0', width: 120 })).toBe(false)
|
||||
expect(colResizable.disabled({ dataIndex: '__actions__', resizable: false })).toBe(true)
|
||||
expect(stkTable.props('headerDrag')).toBeTruthy()
|
||||
wrapper.unmount()
|
||||
})
|
||||
@@ -199,7 +204,9 @@ describe('fms-table 虚拟滚动验证', () => {
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.find('.fms-table').classes()).not.toContain('fms-table--auto-fit-width')
|
||||
expect(wrapper.findComponent(StkTable).props('colResizable')).toBe(true)
|
||||
expect(wrapper.findComponent(StkTable).props('colResizable')).toMatchObject({
|
||||
disabled: expect.any(Function),
|
||||
})
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
|
||||
@@ -63,13 +63,15 @@ describe('fms-module-list components', () => {
|
||||
en.unmount()
|
||||
})
|
||||
|
||||
it('快捷筛选网格列数按条件数收缩(只有一个条件时不占 4 列宽)', () => {
|
||||
it('快捷筛选列数按条件占用收缩(普通条件 200px 一列,区间/日期跨两列)', () => {
|
||||
const one = mount(FmsQuickFilters, {
|
||||
props: { items: [queryItem('b_name')], modelValue: {} },
|
||||
})
|
||||
expect(one.find('.quick-grid').element.style.gridTemplateColumns).toBe(
|
||||
'repeat(1, minmax(0, 288px))',
|
||||
'repeat(1, minmax(0, 200px))',
|
||||
)
|
||||
// 普通单值控件只占一列,不跨列
|
||||
expect(one.find('.quick-field').element.style.gridColumn).toBe('')
|
||||
one.unmount()
|
||||
|
||||
const four = mount(FmsQuickFilters, {
|
||||
@@ -79,9 +81,21 @@ describe('fms-module-list components', () => {
|
||||
},
|
||||
})
|
||||
expect(four.find('.quick-grid').element.style.gridTemplateColumns).toBe(
|
||||
'repeat(4, minmax(0, 288px))',
|
||||
'repeat(4, minmax(0, 200px))',
|
||||
)
|
||||
four.unmount()
|
||||
|
||||
// 日期区间(预设下拉 + 两端)跨两列:列数按占用列求和,宽控件不会被挤到下一行
|
||||
const mixed = mount(FmsQuickFilters, {
|
||||
props: { items: [rangeQueryItem('date'), queryItem('b_canuse')], modelValue: {} },
|
||||
})
|
||||
expect(mixed.find('.quick-grid').element.style.gridTemplateColumns).toBe(
|
||||
'repeat(3, minmax(0, 200px))',
|
||||
)
|
||||
const [wide, narrow] = mixed.findAll('.quick-field')
|
||||
expect(wide.element.style.gridColumn).toBe('span 2')
|
||||
expect(narrow.element.style.gridColumn).toBe('')
|
||||
mixed.unmount()
|
||||
})
|
||||
|
||||
it('快捷筛选「查询」提交草稿、「重置」交给页面处理', async () => {
|
||||
|
||||
@@ -216,7 +216,7 @@ describe('模块权限面板:权限点表(定宽 / 密集)', () => {
|
||||
|
||||
// colResizable + v-model:columns:表格按列宽定宽,拖过的列宽可回写
|
||||
const powerTable = wrapper.findAllComponents({ name: 'FmsTable' })[0]
|
||||
expect(powerTable.props('colResizable')).toBe(true)
|
||||
expect(powerTable.props('colResizable')).toMatchObject({ disabled: expect.any(Function) })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -444,7 +444,7 @@ describe('绑定模块面板:权限区表头与表宽', () => {
|
||||
|
||||
// colResizable + v-model:columns:表格按列宽定宽(不自铺满整栏),拖过的列宽可回写
|
||||
const powerTable = wrapper.findAllComponents({ name: 'FmsTable' })[0]
|
||||
expect(powerTable.props('colResizable')).toBe(true)
|
||||
expect(powerTable.props('colResizable')).toMatchObject({ disabled: expect.any(Function) })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in new issue
Block a user