[Improve] App view table resize icon (#3526)

[Improve] App view table resize icon
diff --git a/streampark-console/streampark-console-webapp/src/components/Table/src/BasicTable.vue b/streampark-console/streampark-console-webapp/src/components/Table/src/BasicTable.vue
index 093e606..1d4722c 100644
--- a/streampark-console/streampark-console-webapp/src/components/Table/src/BasicTable.vue
+++ b/streampark-console/streampark-console-webapp/src/components/Table/src/BasicTable.vue
@@ -16,20 +16,27 @@
       </template>
     </BasicForm>
 
-    <Table
-      ref="tableElRef"
-      v-bind="getBindValues"
-      :rowClassName="getRowClassName"
-      v-show="getEmptyDataIsShowTable"
-      @change="handleTableChange"
-    >
-      <template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
-        <slot :name="item" v-bind="data || {}"></slot>
-      </template>
-      <template #headerCell="{ column }">
-        <HeaderCell :column="column" />
-      </template>
-    </Table>
+    <div ref="tableContainerRef" class="relative">
+      <Table
+        ref="tableElRef"
+        v-bind="getBindValues"
+        :rowClassName="getRowClassName"
+        v-show="getEmptyDataIsShowTable"
+        @change="handleTableChange"
+      >
+        <template
+          #[item]="data"
+          v-for="item in omit(Object.keys($slots), 'insertTable')"
+          :key="item"
+        >
+          <slot :name="item" v-bind="data || {}"></slot>
+        </template>
+        <template #headerCell="{ column }">
+          <HeaderCell :column="column" />
+        </template>
+      </Table>
+      <slot name="insertTable" :tableContainer="tableContainerRef"></slot>
+    </div>
   </div>
 </template>
 <script lang="ts">
@@ -100,6 +107,7 @@
 
       const wrapRef = ref(null);
       const formRef = ref(null);
+      const tableContainerRef = ref(null);
       const innerPropsRef = ref<Partial<BasicTableProps>>();
 
       const { prefixCls } = useDesign('basic-table');
@@ -319,13 +327,19 @@
           return unref(getBindValues).size as SizeType;
         },
       };
-      createTableContext({ ...tableAction, wrapRef, tableFullScreen, getBindValues });
+      createTableContext({
+        ...tableAction,
+        wrapRef,
+        tableFullScreen,
+        getBindValues,
+      });
 
       expose(tableAction);
 
       emit('register', tableAction, formActions);
 
       return {
+        omit,
         formRef,
         tableElRef,
         getBindValues,
@@ -336,6 +350,7 @@
         handleTableChange,
         getRowClassName,
         wrapRef,
+        tableContainerRef,
         tableAction,
         redoHeight,
         getFormProps: getFormProps as any,
diff --git a/streampark-console/streampark-console-webapp/src/views/flink/app/View.vue b/streampark-console/streampark-console-webapp/src/views/flink/app/View.vue
index 641a2dc..9b4a4e8 100644
--- a/streampark-console/streampark-console-webapp/src/views/flink/app/View.vue
+++ b/streampark-console/streampark-console-webapp/src/views/flink/app/View.vue
@@ -49,7 +49,7 @@
   } from './components/State';
   import { useSavepoint } from './hooks/useSavepoint';
   import { useAppTableColumns } from './hooks/useAppTableColumns';
-
+  import AppTableResize from './components/AppResize.vue';
   const { t } = useI18n();
   const optionApps = {
     starting: new Map(),
@@ -62,7 +62,7 @@
 
   const yarn = ref<Nullable<string>>(null);
   const currentTablePage = ref(1);
-  const { onTableColumnResize, getAppColumns } = useAppTableColumns();
+  const { onTableColumnResize, tableColumnWidth, getAppColumns } = useAppTableColumns();
   const { openSavepoint } = useSavepoint(handleOptionApp);
   const [registerStartModal, { openModal: openStartModal }] = useModal();
   const [registerStopModal, { openModal: openStopModal }] = useModal();
@@ -348,6 +348,13 @@
           <TableAction v-bind="getTableActions(record, currentTablePage)" />
         </template>
       </template>
+      <template #insertTable="{ tableContainer }">
+        <AppTableResize
+          :table-container="tableContainer"
+          :resize-min="100"
+          v-model:left="tableColumnWidth.jobName"
+        />
+      </template>
     </BasicTable>
     <StartApplicationModal @register="registerStartModal" @update-option="handleOptionApp" />
     <StopApplicationModal @register="registerStopModal" @update-option="handleOptionApp" />
diff --git a/streampark-console/streampark-console-webapp/src/views/flink/app/components/AppResize.vue b/streampark-console/streampark-console-webapp/src/views/flink/app/components/AppResize.vue
new file mode 100644
index 0000000..2aa8d43
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/views/flink/app/components/AppResize.vue
@@ -0,0 +1,156 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<script setup lang="ts">
+  import { EllipsisOutlined } from '@ant-design/icons-vue';
+  import { isNil } from 'lodash-es';
+  import { ref, computed, onMounted, onUnmounted } from 'vue';
+  defineOptions({ name: 'AppResize' });
+  const props = withDefaults(
+    defineProps<{
+      resizeMin?: number;
+      left: number;
+      tableContainer: HTMLElement | null;
+    }>(),
+    {
+      resizeMin: 100,
+    },
+  );
+  const emit = defineEmits(['resizeEnd', 'update:left']);
+  const domRef = ref<HTMLElement | null>(null);
+  const getStyle = computed(() => {
+    return {
+      left: `${props.left + 10}px`,
+      top: '50px',
+    };
+  });
+  const isMove = ref(false);
+
+  function getResizeLeft(e: MouseEvent): number {
+    const getOffsetLeft = (elem: HTMLElement | null): number => {
+      let innerOffsetLeft = 0;
+      let currentElem = elem;
+      do {
+        if (!isNil(currentElem!.offsetLeft)) innerOffsetLeft += currentElem!.offsetLeft;
+      } while ((currentElem = currentElem!.offsetParent as HTMLElement));
+      return Math.floor(innerOffsetLeft);
+    };
+
+    const offsetLeft = getOffsetLeft(props.tableContainer);
+    const maxResize = props.tableContainer!.getBoundingClientRect().width - 510;
+    let newLeft = e.pageX - offsetLeft;
+    if (newLeft > maxResize) newLeft = maxResize;
+
+    if (newLeft < props.resizeMin) newLeft = props.resizeMin;
+
+    return newLeft;
+  }
+
+  function startMove() {
+    isMove.value = true;
+  }
+
+  function move(e: MouseEvent) {
+    if (!isMove.value) return;
+    e.preventDefault();
+    const left = getResizeLeft(e);
+    emit('update:left', left);
+  }
+
+  function mouseup(e: MouseEvent) {
+    isMove.value = false;
+    if (e.target !== domRef.value) {
+      e.stopPropagation();
+      emit('resizeEnd', { left: props.left });
+    }
+  }
+
+  onMounted(() => {
+    window.addEventListener('mouseup', mouseup);
+    window.addEventListener('mousemove', move);
+  });
+  onUnmounted(() => {
+    window.removeEventListener('mouseup', mouseup);
+    window.removeEventListener('mousemove', move);
+  });
+</script>
+
+<template>
+  <div ref="domRef" class="resize app-vertical" :style="getStyle" @mousedown="startMove">
+    <div class="resize-handle app-handle-vertical">
+      <EllipsisOutlined />
+    </div>
+  </div>
+</template>
+
+<style lang="less">
+  :root {
+    --resize-border-color: #f0f0f0;
+    --resize-handle-border: #e8e8e8;
+    --resize-background-color: #fbfbfb;
+  }
+
+  [data-theme='dark']:root {
+    --resize-border-color: #303030;
+    --resize-handle-border: #303030;
+    --resize-background-color: #1d1d1d;
+  }
+
+  .resize {
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 10;
+    background: transparent;
+
+    &.app-vertical {
+      width: 7px;
+      height: calc(100% - 95px);
+      border-left: 1px solid var(--resize-border-color);
+      cursor: col-resize;
+    }
+  }
+
+  .resize-handle {
+    position: relative;
+    top: -2px;
+    left: 50%;
+    width: 30px;
+    height: 10px;
+    margin-right: 15px;
+    border: 1px solid var(--resize-handle-border);
+    border-radius: 2px;
+    background: var(--resize-background-color);
+    line-height: 10px;
+    text-align: center;
+    pointer-events: none;
+
+    &.app-handle-vertical {
+      top: 50%;
+      left: auto;
+      margin-top: -15px;
+      margin-right: auto;
+      margin-left: 5px;
+      transform: rotate(90deg);
+      transform-origin: left top;
+    }
+
+    span {
+      position: relative;
+      top: -2px;
+    }
+  }
+</style>
diff --git a/streampark-console/streampark-console-webapp/src/views/flink/app/hooks/useAppTableColumns.ts b/streampark-console/streampark-console-webapp/src/views/flink/app/hooks/useAppTableColumns.ts
index 95058e9..82f1e39 100644
--- a/streampark-console/streampark-console-webapp/src/views/flink/app/hooks/useAppTableColumns.ts
+++ b/streampark-console/streampark-console-webapp/src/views/flink/app/hooks/useAppTableColumns.ts
@@ -40,7 +40,7 @@
     const dataIndexStr = columns?.dataIndex.toString() ?? '';
     if (Reflect.has(tableColumnWidth.value, dataIndexStr)) {
       // when table column width changed, save it to table column width ref
-      tableColumnWidth.value[dataIndexStr] = width;
+      tableColumnWidth.value[dataIndexStr] = width < 100 ? 100 : width;
     }
   }
 
@@ -53,7 +53,7 @@
       resizable: true,
       width: unref(tableColumnWidth).jobName,
     },
-    { title: t('flink.app.flinkVersion'), dataIndex: 'flinkVersion', width: 110 },
+    { title: t('flink.app.flinkVersion'), dataIndex: 'flinkVersion' },
     { title: t('flink.app.tags'), ellipsis: true, dataIndex: 'tags', width: 150 },
     {
       title: t('flink.app.runStatus'),
@@ -97,5 +97,5 @@
     },
     { title: t('flink.app.owner'), dataIndex: 'nickName', width: unref(tableColumnWidth).nickName },
   ]);
-  return { getAppColumns, onTableColumnResize };
+  return { getAppColumns, onTableColumnResize, tableColumnWidth };
 };