jgy-public-component
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. 宿主前置条件
- 已安装
element-plus(peerDependency) - vite 已代理
/bis-app到后端 - 后端具备同一套 bis 产品详情接口(
/bis-app/comm/sku/doDetail等) 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
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 | |
| 0.0.49 | ^3.3.0 | MIT | |
| 0.0.48 | ^3.3.0 | MIT | |
| 0.0.47 | ^3.3.0 | MIT | |
| 0.0.46 | ^3.3.0 | MIT | |
| 0.0.45 | ^3.3.0 | MIT | |
| 0.0.44 | ^3.3.0 | MIT | |
| 0.0.43 | ^3.3.0 | MIT | |
| 0.0.42 | ^3.3.0 | MIT | |
| 0.0.41 | ^3.3.0 | MIT | |
| 0.0.40 | ^3.3.0 | MIT | |
| 0.0.39 | ^3.3.0 | MIT | |
| 0.0.38 | ^3.3.0 | MIT | |
| 0.0.37 | ^3.3.0 | MIT | |
| 0.0.36 | ^3.3.0 | MIT | |
| 0.0.35 | ^3.3.0 | MIT | |
| 0.0.34 | ^3.3.0 | MIT | |
| 0.0.33 | ^3.3.0 | MIT | |
| 0.0.32 | ^3.3.0 | MIT | |
| 0.0.31 | ^3.3.0 | MIT | |
| 0.0.30 | ^3.3.0 | MIT | |
| 0.0.29 | ^3.3.0 | MIT | |
| 0.0.28 | ^3.3.0 | MIT | |
| 0.0.25 | ^3.3.0 | MIT | |
| 0.0.24 | ^3.3.0 | MIT | |
| 0.0.23 | ^3.3.0 | MIT |
Showing the latest 30 of 51. See every release on npm