jgy-public-component

JGY 公共 Vue 3 组件库(JAdminLayout / JSidebar / 列表搜索头 / 单据详情 / 审批流)
7,600
Install
npm install jgy-public-component
Latest Version:0.0.54
Vue:3
License:MIT
Last Updated:Oct 23, 2026
Links: npm
Maintainer: ekko410

jgy-public-component

JGY 公共 Vue 3 组件库,基于 Vue 3 + TypeScript + Vite + SCSS 构建。

安装

npm install jgy-public-component

使用方式

全量引入

import { createApp } from 'vue'
import App from './App.vue'
import JgyComponents from 'jgy-public-component'
import 'jgy-public-component/dist/style.css'

const app = createApp(App)
app.use(JgyComponents)
app.mount('#app')

按需引入

<template>
  <JButton type="primary" @click="handleClick">点击我</JButton>
</template>

<script setup lang="ts">
import { JButton } from 'jgy-public-component'
import 'jgy-public-component/dist/style.css'
</script>

组件列表

组件名 说明
JButton 按钮
JIconRender 图标渲染
JUploadImg 图片上传
JSearchHeader 筛选操作条(折叠筛选 + 新增/删除/搜索)
JListSearchHeader 列表页搜索头(Tabs + 筛选 + 操作 + 导出/列设置)
JSearchInputDialog 关键词搜索(类型下拉 + 输入 + 批量弹窗)
JRemoteSelect 远程搜索下拉(分页滚动 + 联动前置校验 + 编辑回显)
JSupplierCycleSelect 供应商周期选择(内采 / 抬头 / 生命周期,需注入 request)
JSupplierCycleDisplay 供应商周期展示(按 keyword 查询,展示逻辑同选择器)
JProductSelectDialog 产品选择弹窗(跨页多选 + 已选 Popover)
JImportExcel Excel 导入(前端解析 rows 后提交)
JImportData 公共导入弹窗(FormData 上传文件 + 导入模式)
JSidebar / JSideMenuItem 侧边栏菜单(有 icon 就展示;折叠 hover popup 独立字号/宽度)
JAdminLayout 后台布局(内容区确定高度,列表页用 height: 100% 铺满,不要 calc(100vh - N))
JProductDetailDialog / JSkuLink 产品详情 / SKU 链接(需注入 request)
DocNoLink / DocDetailHost 单号链接 + 全局单据详情(需 createDocDetail 注册)
JRelatedDocs 关联单据子 Tab 容器(需 createRelatedDocs 注入接口)
JApprovalFlow 审批进度时间线(需注入 request)
JApprovalActions 跟审批配套的按钮(审核/转审/回退/加签 + 弹窗,需 createApprovalFlow)

工具方法:数据转换、按钮权限、状态条数 / Tabs、exportStyledExcel 前端导出 Excel、路由守卫。

完整 Props / 事件 / 插槽、业务组件接入步骤与排错,见 docs/index.html(可离线打开)。
列表筛选四件套(JListSearchHeader / JRemoteSelect / JSupplierCycleSelect / JSupplierCycleDisplay)见 docs/JListSearchHeader-JRemoteSelect-JSupplierCycle-使用文档.md。
审批:JApprovalFlow(时间线)+ JApprovalActions(按钮)见 docs/JApprovalFlow-使用文档.md。
SCM 等业务系统本地详情弹窗接入审批见 docs/JApprovalFlow-业务详情接入.md。
公共导入弹窗 JImportData 见 docs/JImportData-使用文档.md。

JListSearchHeader 快速示例

列表页完整头部,API 对齐 SCM 原 SearchHeader:

<template>
  <JListSearchHeader
    v-model="activeTab"
    :show-more-filter="false"
    @search="handleSearch"
    @reset="handleReset"
    @download="handleDownload"
    @settings-save="handleSettingsSave"
    @restore-default="handleRestoreDefault"
    @tab-change="handleTabChange"
  >
    <template #tabs>
      <el-tab-pane label="全部" name="all" />
      <el-tab-pane label="待处理" name="pending" />
    </template>

    <template #filter>
      <el-form inline>
        <el-form-item>
          <el-input v-model="keyword" placeholder="关键词" />
        </el-form-item>
      </el-form>
    </template>

    <template #actions>
      <el-button type="primary">审批</el-button>
    </template>

    <template #settings>
      <!-- 列设置内容 -->
    </template>
  </JListSearchHeader>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { JListSearchHeader } from 'jgy-public-component'
</script>

JSkuLink + JProductDetailDialog(点击 SKU 弹产品详情)

列表场景:多行 JSkuLink + 页面底部一个 JProductDetailDialog,不要每行挂一个弹窗。

1. 入口注入 request(推荐,全项目一次)
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import request from '@/api'
import JgyComponents, { provideProductRequest } from 'jgy-public-component'
import 'jgy-public-component/dist/style.css'

const app = createApp(App)
app.use(JgyComponents)

// 把项目统一的 axios 封装注入给产品详情组件
provideProductRequest(app, (config) => request(config))

app.mount('#app')
2. 页面用法
<template>
  <!-- 表格里:只放链接 -->
  <JSkuLink :code="row.sku" @open="openProductDetailDialog" />

  <!-- 页面底部:只挂一个弹窗 -->
  <JProductDetailDialog
    v-model:visible="productDetailDialogVisible"
    :code="productDetailDialogCode"
    type="sku"
  />
  <!-- 若未在 main.ts provide,需额外传 :request="request" -->
</template>

<script setup lang="ts">
import {
  JSkuLink,
  JProductDetailDialog,
  useProductDetailDialog,
} from 'jgy-public-component'

const {
  productDetailDialogVisible,
  productDetailDialogCode,
  openProductDetailDialog,
} = useProductDetailDialog()
</script>
3. 单页传入 request(不全局 provide 时)
<script setup lang="ts">
import request from '@/api'
import { JProductDetailDialog, JSkuLink, useProductDetailDialog } from 'jgy-public-component'
// ...
</script>

<template>
  <JSkuLink :code="row.sku" @open="openProductDetailDialog" />
  <JProductDetailDialog
    v-model:visible="productDetailDialogVisible"
    :code="productDetailDialogCode"
    type="sku"
    :request="request"
  />
</template>
4. Props / 事件

JSkuLink

属性 类型 默认 说明
code string | null - SKU/编码
emptyText string '-' 无编码时展示
事件 参数 说明
open (code: string) 有编码且点击时触发

JProductDetailDialog

属性 类型 默认 说明
visible / v-model:visible boolean false 显隐
code string '' sku_code / spu_code
type 'sku' | 'spu' 'sku' 详情接口类型
id number | string - 可选行 id
request JProductRequestFn - 可选;未传则用全局 provide

主题色(可选)

:root {
  --j-sku-link-color: #409eff;
}
5. 宿主前置条件
  1. 已安装 element-plus(peerDependency)
  2. vite 已代理 /bis-app 到后端
  3. 后端具备同一套 bis 产品详情接口(/bis-app/comm/sku/doDetail 等)
  4. request 返回体已被拦截器解包为 { code, data, msg }

JSearchInputDialog 快速示例

<template>
  <JSearchInputDialog
    v-model:type="filters.keywordType"
    v-model:keyword="filters.keyword"
    :options="[
      { label: 'SKU', value: 'sku' },
      { label: '品名', value: 'product_name' },
    ]"
    @confirm="handleSearch"
    @search="handleSearch"
  />
</template>

<script setup lang="ts">
import { reactive } from 'vue'
import { JSearchInputDialog } from 'jgy-public-component'

const filters = reactive({
  keywordType: 'sku',
  keyword: '',
})

const handleSearch = () => {
  const params: Record<string, any> = {}
  if (filters.keyword) {
    params[filters.keywordType] = filters.keyword // { sku: 'sku1,sku2' }
  }
}
</script>

JRemoteSelect 快速示例

接口由 fetchApi 注入,组件负责分页滚动加载与联动前置校验。

<template>
  <!-- 无联动的独立下拉:必须加 allow-empty,否则因 params 为空而一直禁用 -->
  <JRemoteSelect
    v-model="form.warehouseId"
    :fetch-api="getWarehouseList"
    label-field="warehouse_name"
    value-field="id"
    immediate
    allow-empty
  />

  <!-- 联动:库区依赖仓库,仓库没选时禁用并提示 -->
  <JRemoteSelect
    v-model="form.areaId"
    :fetch-api="getAreaList"
    :params="{ warehouse_id: form.warehouseId }"
    :required-params="['warehouse_id']"
    label-field="area_name"
    depends-placeholder="请先选择仓库"
    auto-clear-on-params-change
  />
</template>

<script setup lang="ts">
import { reactive } from 'vue'
import { JRemoteSelect } from 'jgy-public-component'
import request from '@/api'

const form = reactive({ warehouseId: null, areaId: null })

// 组件自动传入 pageIndex / pageSize,远程搜索时再带上 keyword
const getWarehouseList = (params: Record<string, unknown>) =>
  request({ url: '/wms-app/warehouse/list', method: 'post', data: params })

const getAreaList = (params: Record<string, unknown>) =>
  request({ url: '/wms-app/area/list', method: 'post', data: params })
</script>

编辑页只拿到 id、没有完整选项时,用 initial-label 回显名称:

<JRemoteSelect
  v-model="form.supplierId"
  :fetch-api="getSupplierList"
  :initial-label="detail.supplier_name"
  allow-empty
/>

JSupplierCycleSelect 快速示例

供应商选择(含生命周期 / 抬头 / 内采标签)。接口写死,请求走 provideProductRequest,与产品详情同一套注入。完整说明见 src/components/JSupplierCycleSelect/README.md。

<template>
  <JSupplierCycleSelect
    v-model="form.supplier_codes"
    :params="{ sku_code: form.sku_code }"
    @change="onSupplierChange"
  />
</template>

<script setup lang="ts">
import { reactive } from 'vue'
import { JSupplierCycleSelect } from 'jgy-public-component'
import type { JSupplierCycleItem, JSupplierCycleSelectValue } from 'jgy-public-component'

const form = reactive({ sku_code: '', supplier_codes: '' })

const onSupplierChange = (
  value: JSupplierCycleSelectValue,
  options: JSupplierCycleItem[],
) => {
  console.log(value, options)
}
</script>

v-model 统一为逗号分隔的 supplier_code 字符串(空为 '')。默认多选,需点确定;:multiple="false" 时点行即选。

JProductSelectDialog 快速示例

弹窗只负责分页请求、跨页多选与已选管理;筛选表单和表格列由插槽提供。

<template>
  <JProductSelectDialog
    v-model="dialogVisible"
    v-model:query="query"
    :fetch-api="getProductList"
    row-key="sku_code"
    @confirm="onConfirm"
  >
    <template #filters="{ query, search, reset, loading }">
      <el-form inline>
        <el-form-item label="SKU">
          <el-input v-model="query.sku_code" clearable @keyup.enter="search" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" :loading="loading" @click="search">查询</el-button>
          <el-button @click="reset">重置</el-button>
        </el-form-item>
      </el-form>
    </template>

    <template #columns>
      <el-table-column prop="sku_code" label="SKU" width="160" />
      <el-table-column prop="product_name" label="品名" show-overflow-tooltip />
    </template>
  </JProductSelectDialog>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue'
import { JProductSelectDialog } from 'jgy-public-component'
import request from '@/api'

const dialogVisible = ref(false)
const query = reactive({ sku_code: '' })

const getProductList = (params: Record<string, any>) =>
  request({ url: '/bis-app/comm/sku/doList', method: 'post', data: params })

// rows 为跨页累计的全部已选行
const onConfirm = (rows: Record<string, any>[]) => {
  console.log(rows)
}
</script>

用 v-model 控制显隐(不是 v-model:visible)。弹窗关闭后已选、分页会全部清空,需要保留请在父级自行缓存。

状态条数 Tabs 快速示例

统一处理「各状态返回条数」接口,并解决「全部」Tab 与 status 参数的换算。

<template>
  <JListSearchHeader v-model="activeTab" @search="onSearch" @tab-change="onSearch">
    <template #tabs>
      <el-tab-pane
        v-for="tab in tabs"
        :key="tab.tab_name"
        :name="tab.tab_name"
        :label="`${tab.name}(${tab.count})`"
      />
    </template>
  </JListSearchHeader>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import {
  unwrapStatusCountList,
  mapStatusCountToTabs,
  buildFallbackStatusTabs,
  resolveListStatusParam,
  syncActiveTabIfAll,
} from 'jgy-public-component'

const activeTab = ref('all')
// 接口失败时仍有 Tab 可点
const tabs = ref(buildFallbackStatusTabs([
  { value: -1, label: '草稿' },
  { value: 100, label: '待审核' },
]))

const loadTabs = async () => {
  const list = unwrapStatusCountList(await getStatusCounts())
  if (list.length) {
    tabs.value = mapStatusCountToTabs(list)
    syncActiveTabIfAll(activeTab, tabs.value, { asString: true })
  }
}

const onSearch = () => {
  // 「全部」返回 undefined,即不传 status
  loadList({ status: resolveListStatusParam(activeTab.value) })
}

onMounted(() => { loadTabs(); onSearch() })
</script>

注意 0 和 '0' 会被判为「全部」。若业务里 status=0 是真实状态,请避开 0 取值。

导出 Excel

exportStyledExcel 为纯前端导出,exceljs 是 peerDependency,需宿主项目安装。

import { exportStyledExcel } from 'jgy-public-component'

// 对象的 key 即表头,顺序即列顺序(以第一行为准)
const rows = list.value.map((item) => ({
  'SKU': item.sku_code,
  '品名': item.product_name,
  '数量': item.qty,
}))

await exportStyledExcel(rows, '产品列表.xlsx', '产品列表')

// 可选:自定义表头色(ARGB,带 FF 前缀)与列宽
await exportStyledExcel(rows, 'data.xlsx', 'Sheet1', {
  headerFill: 'FF67C23A',
  columnWidths: [18, 30, 10],
})

空数组会抛「没有可导出的数据」,调用处需 try/catch。

本地开发

# 安装依赖
npm install

# 启动调试页面
npm run dev

# 构建组件库
npm run build:lib

发布到 npm

当前待发布版本见 RELEASE.md。

# 构建(publish 前必跑,dist 不入 git)
npm run build:lib

# 登录(首次需要)
npm login

# 发布(版本号以 package.json 为准,勿重复发布已占用版本)
npm publish

License

MIT

Related Packages

seeroad-ui

seeroad project ui library

3,733
ui
smart-ui

High Quality Mobile UI Componets by Vue.js 2.

1,883 11
ui
hea-ui

hea-ui is a h5 ui component for vue

1,520 14
ui
canna-ui-vue

shims-vue.d.ts declare module 'canna-ui-vue'

674
ui

Version History

Version Released Vue License
0.0.54 ^3.3.0 MIT
0.0.53 ^3.3.0 MIT
0.0.52 ^3.3.0 MIT
0.0.51 ^3.3.0 MIT
0.0.50 ^3.3.0 MIT