Merge remote-tracking branch 'origin/master'

master
wangjunjie 2026-01-07 19:16:51 +08:00
commit e4d891d974
11 changed files with 813 additions and 10 deletions

Binary file not shown.

View File

@ -122,8 +122,8 @@ Evt.on('logout', (_) => {
.filter((it) => it.name !== SpecialPage.Main && it.name !== SpecialPage.Login && it.name !== SpecialPage.NotFound && it.name !== SpecialPage.Home)
.map((it) => it.name as string)
removeRoutes(routes)
router.push({replace: true, name: SpecialPage.Login}).then((r) => {
console.log(r)
router.push({replace: true, name: SpecialPage.Login}).then(() => {
// console.log(r)
})
})

View File

@ -0,0 +1,277 @@
<script lang="ts" setup>
import Utils from '@/common/utils'
import { elIcons } from '@/common/element/element.ts'
const props = withDefaults(defineProps<{
modelValue?: string | string[],
multiple?: boolean,
placeholder?: string,
displayField: string,
columns: {
label: string
prop: string
}[]
loader: (param: any) => Promise<G.PageResult<any>>
}>(), {
placeholder: '请选择',
multiple: false,
})
const emits = defineEmits([ 'update:modelValue' ])
const totalCount = ref(0)
const searchForm = Utils.resetAble(reactive<Record<string, any> & G.PageParam>({
keywords: '',
current: 1,
size: 20,
orders: 'id',
}))
const tableDataList = ref<Record<string, any> & { id: string }[]>([])
const searching = ref(false)
const selectRows = Utils.resetAble(reactive<string[]>([]))
function paging() {
searching.value = true
return props.loader(searchForm)
.then(res => {
totalCount.value = res.total ?? 0
const value = res.records ?? []
tableDataList.value = value
return value
})
.finally(() => {
searching.value = false
})
}
function reset() {
searchForm.$reset()
paging()
}
function onOpen() {
if (props.multiple) {
if (props.modelValue != null && props.modelValue.length > 0) {
selectRows.$reset(props.modelValue as string[])
}
} else {
if (props.modelValue != null && props.modelValue.length > 0) {
selectRows.$reset([ props.modelValue as string ])
}
}
paging()
}
function onClose() {
}
function onClear() {
selectRows.$reset([])
if (props.multiple) {
emits('update:modelValue', selectRows)
} else {
emits('update:modelValue', selectRows.length > 0 ? selectRows[0] : null)
}
}
function visibleChangeHandler(val: boolean) {
if (val) onOpen()
else onClose()
}
function rowClick(val: Record<string, any> & { id: string }) {
let value: string | string[] | null
if (props.multiple) {
const indexOf = selectRows.indexOf(val.id)
if (indexOf === -1) {
selectRows.push(val.id)
} else {
selectRows.splice(indexOf, 1)
}
value = [ ...selectRows ]
} else {
const indexOf = selectRows.indexOf(val.id)
if (indexOf === -1) {
selectRows.$reset([ val.id ])
} else {
selectRows.$reset([])
}
value = val.id
}
emits('update:modelValue', value)
}
const selectHeader_indeterminate = computed(() => {
return selectRows.length > 0 && selectRows.length < tableDataList.value.length
})
const selectHeader_checked = computed({
get() {
return selectRows.length === tableDataList.value.length
},
set(val) {
if (props.multiple) {
if (val) {
const newData = tableDataList.value.map(it => it.id)
selectRows.$reset([ ...new Set([ ...selectRows, ...newData ]) ])
} else {
const newData = tableDataList.value.map(it => it.id)
selectRows.$reset(selectRows.filter(it => !newData.includes(it)))
}
emits('update:modelValue', selectRows)
} else {
const newData = tableDataList.value.map(it => it.id)
selectRows.$reset(selectRows.filter(it => !newData.includes(it)))
emits('update:modelValue', selectRows.length > 0 ? selectRows[0] : null)
}
},
})
const displayData = computed(() => {
if (props.multiple) {
return `已选 ${selectRows.length}`
}
return tableDataList.value
.filter(it => selectRows.includes(it.id))
.map(it => ((it as Record<string, any>) [props.displayField] as any) ?? '')
.join(' ')
})
</script>
<template>
<ElDropdown class="drop-table"
closable
placement="bottom"
trigger="click"
@visible-change="visibleChangeHandler">
<ElInput :model-value="displayData" :placeholder="placeholder" readonly>
<template #append>
<ElIcon class="clear-btn" @click.stop="onClear">
<ElIconCircleClose/>
</ElIcon>
</template>
</ElInput>
<template #dropdown>
<div class="drop-table-content">
<ElForm inline @submit.prevent="paging">
<slot name="search_form">
<ElFormItem>
<ElInput
v-model="searchForm.keywords"
placeholder="请输入要搜索的内容"/>
</ElFormItem>
</slot>
<ElFormItem>
<ElButton :icon="elIcons.Search" :loading="searching" native-type="submit" type="primary">搜索</ElButton>
<ElButton :icon="elIcons.Refresh" :loading="searching" @click="reset"></ElButton>
</ElFormItem>
</ElForm>
<ElDivider class="drop-table-content-divider"/>
<ElTable
ref="tableRef"
:data="tableDataList"
cell-class-name="table-cell"
class="table-list"
empty-text="暂无数据"
header-row-class-name="table-header"
row-key="id"
@row-click="rowClick"
>
<ElTableColumn width="50">
<template #default="{row}">
<ElCheckbox :model-value="selectRows.includes(row.id)"/>
</template>
<template #header>
<ElCheckbox v-model="selectHeader_checked" :indeterminate="selectHeader_indeterminate"/>
</template>
</ElTableColumn>
<ElTableColumn label="#" type="index"/>
<ElTableColumn
v-for="(item, i) in columns"
:key="'a-drop-table-'+i"
:label="item.label"
:prop="item.prop"/>
</ElTable>
<ElDivider class="drop-table-content-divider"/>
<ElPagination
v-model:current-page="searchForm.current"
v-model:page-size="searchForm.size"
:hide-on-single-page="false"
:page-sizes="[10, 20, 50, 100, 500]"
:teleported="false"
:total="totalCount"
layout="->, sizes, total, prev, pager, next"
@change="paging"/>
</div>
</template>
</ElDropdown>
</template>
<style lang="stylus" scoped>
.clear-btn {
cursor: pointer;
&:hover {
color: #1C6EFF
}
}
.drop-table-content {
padding: 18px;
display: flex;
flex-direction: column;
}
.drop-table-content-divider {
margin 10px 0
}
.table-list {
flex 1;
margin 0 0 20px 0
width 100%
:deep(.table-header) {
color #454C59
th {
background-color #EDF1F7
font-weight 500
position relative
& > div {
display flex
gap 5px
align-items center
}
&:not(:first-child) > div::before {
position: absolute;
top: 50%;
left: 1px;
width: 1px;
background-color: #D3D7DE;
transform: translateY(-50%);
content: "";
height 50%
}
}
}
:deep(.table-cell) {
color #2F3540
}
.action-btn {
width 100%
display flex
flex-wrap wrap
& > button {
margin 0
}
}
}
</style>

View File

@ -32,6 +32,7 @@ declare module 'vue' {
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElIconCircleClose: typeof import('@element-plus/icons-vue')['CircleClose']
ElIconPicture: typeof import('@element-plus/icons-vue')['Picture']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
@ -84,6 +85,7 @@ declare global {
const ElFormItem: typeof import('element-plus/es')['ElFormItem']
const ElHeader: typeof import('element-plus/es')['ElHeader']
const ElIcon: typeof import('element-plus/es')['ElIcon']
const ElIconCircleClose: typeof import('@element-plus/icons-vue')['CircleClose']
const ElIconPicture: typeof import('@element-plus/icons-vue')['Picture']
const ElImage: typeof import('element-plus/es')['ElImage']
const ElInput: typeof import('element-plus/es')['ElInput']

View File

@ -71,7 +71,7 @@
</template>
</ElTableColumn>
<!-- <ElTableColumn label="计量单位" prop="unit" /> -->
<ElTableColumn label="是否为成品" width="100">
<!-- <ElTableColumn label="是否为成品" width="100">
<template #default="{ row }">
{{ row.fg ? "是" : "否" }}
</template>
@ -86,7 +86,7 @@
<template #default="{ row }">
{{ row.rg ? "是" : "否" }}
</template>
</ElTableColumn>
</ElTableColumn> -->
<ElTableColumn label="是否可用" prop="canuse">
<template #default="{ row }">
{{ row.canuse ? "是" : "否" }}
@ -114,12 +114,12 @@
</template>
<script lang="ts" setup>
import GoodsApi from "@/pages/gds/goods/goods-api.ts";
import GoodsForm from "@/pages/gds/goods/GoodsForm.vue";
import Page from "@/components/page/Page.vue";
import { elIcons } from "@/common/element/element.ts";
import Utils from "@/common/utils";
import AppApi from "@/common/app/app-api.ts";
import GoodsApi from '@/pages/gds/goods/goods-api.ts'
import GoodsForm from '@/pages/gds/goods/GoodsForm.vue'
import Page from '@/components/page/Page.vue'
import { elIcons } from '@/common/element/element.ts'
import Utils from '@/common/utils'
import AppApi from '@/common/app/app-api.ts'
const totalCount = ref(0);
const tableData = Utils.resetAble(reactive<GoodsTypes.SearchGoodsResult[]>([]));

View File

@ -0,0 +1,224 @@
<template>
<Page>
<ElForm v-show="showSearchForm" inline @submit.prevent="paging">
<ElFormItem label="编码">
<ElInput
v-model="searchForm.sn"
placeholder="编码"/>
</ElFormItem>
<ElFormItem label="终产品">
<ElInput
v-model="searchForm.goodsId"
placeholder="终产品"/>
</ElFormItem>
<ElFormItem label="工艺名称">
<ElInput
v-model="searchForm.craftName"
placeholder="工艺名称"/>
</ElFormItem>
<ElFormItem label="工艺版本号">
<ElInput
v-model="searchForm.craftVer"
placeholder="工艺版本号"/>
</ElFormItem>
<ElFormItem label="是否可用">
<ElCheckbox v-model="searchForm.canuse"/>
</ElFormItem>
<ElFormItem>
<ElButton :icon="elIcons.Search" :loading="searching" native-type="submit" type="primary">搜索</ElButton>
<ElButton :icon="elIcons.Refresh" :loading="searching" @click="reset"></ElButton>
</ElFormItem>
</ElForm>
<div class="tool-bar">
<ElButton :icon="elIcons.Plus" type="primary" @click="addHandler"></ElButton>
<ElButton :icon="elIcons.Filter" type="default" @click="showSearchForm = !showSearchForm"/>
</div>
<ElTable v-loading="searching" :data="tableData"
cell-class-name="table-cell"
class="table-list"
empty-text="暂无数据"
header-row-class-name="table-header"
row-key="id">
<ElTableColumn label="编码" prop="sn"/>
<ElTableColumn label="产品名称" prop="goodsName"/>
<ElTableColumn label="产品编码" prop="goodsSn"/>
<ElTableColumn label="工艺名称" prop="craftName"/>
<ElTableColumn label="工艺版本号" prop="craftVer"/>
<ElTableColumn label="工艺类型" prop="craftCategoryTxt"/>
<ElTableColumn label="是否可用" prop="canuse">
<template #default="{row}">
<ElSwitch v-model="row.canuse" :disabled="row.id == '1'" @change="disabledCraftHandler($event,row.id)"/>
</template>
</ElTableColumn>
<ElTableColumn label="备注" prop="memo"/>
<ElTableColumn label="操作" width="180">
<template #default="scope">
<div class="action-btn">
<!-- <ElPopconfirm
confirm-button-text="是"
cancel-button-text="否"
confirm-button-type="danger"
cancel-button-type="primary"
placement="top"
title="是否删除当前数据?"
width="180"
@confirm="delHandler(scope)">
<template #reference>
<ElButton text type="danger" :loading="deling">删除</ElButton>
</template>
</ElPopconfirm> -->
<!-- <ElButton text type="primary" @click="modifyHandler(scope)"></ElButton> -->
<ElButton text type="primary" @click="modifyHandler(scope)"></ElButton>
</div>
</template>
</ElTableColumn>
</ElTable>
<ElPagination
v-model:current-page="searchForm.current"
v-model:page-size="searchForm.size"
:hide-on-single-page="false"
:page-sizes="[10, 20, 50, 100, 500]"
:teleported="false"
:total="totalCount"
layout="->, sizes, total, prev, pager, next"
@change="paging"/>
<CraftForm ref="craftForm" @edit-succ="paging"/>
</Page>
</template>
<script lang="ts" setup>
import CraftApi from '@/pages/mfg/craft/craft-api.ts'
import CraftForm from '@/pages/mfg/craft/CraftForm.vue'
import Page from '@/components/page/Page.vue'
import { elIcons } from '@/common/element/element.ts'
import Utils from '@/common/utils'
import { ElMessage } from 'element-plus'
const totalCount = ref(0)
const tableData = Utils.resetAble(reactive<CraftTypes.SearchCraftResult[]>([]))
const searchForm = Utils.resetAble(reactive<CraftTypes.SearchCraftParam>({
current: 1,
size: 20,
}))
const searching = ref(false)
const deling = ref(false)
const showSearchForm = ref(true)
const craftFormIns = useTemplateRef<InstanceType<typeof CraftForm>>('craftForm')
function showDialog(data?: CraftTypes.SearchCraftResult) {
craftFormIns.value?.open(data)
}
/*
function delHandler({row}: { row: CraftTypes.SearchCraftResult }) {
deling.value = true
CraftApi.del([ row.id! ])
.then(() => {
ElMessage.success('删除成功')
paging()
})
.finally(() => {
deling.value = false
})
}
*/
function modifyHandler({row}: { row: CraftTypes.SearchCraftResult }) {
showDialog(row)
}
function addHandler() {
showDialog()
}
function reset() {
searchForm.$reset()
paging()
}
function paging() {
searching.value = true
CraftApi.paging(searchForm)
.then(res => {
totalCount.value = res.data?.total ?? 0
tableData.$reset(res.data?.records ?? [])
})
.finally(() => {
searching.value = false
})
}
function disabledCraftHandler(val: string | number | boolean, id: string) {
searching.value = true
CraftApi.disable(id, val as boolean)
.then(() => {
ElMessage.success(val ? '禁用成功' : '启用成功')
paging()
})
}
onMounted(() => {
paging()
})
</script>
<style lang="stylus" scoped>
.table-list {
flex 1;
margin 0 0 20px 0
width 100%
:deep(.table-header) {
color #454C59
th {
background-color #EDF1F7
font-weight 500
position relative
& > div {
display flex
gap 5px
align-items center
}
&:not(:first-child) > div::before {
position: absolute;
top: 50%;
left: 1px;
width: 1px;
background-color: #D3D7DE;
transform: translateY(-50%);
content: "";
height 50%
}
}
}
:deep(.table-cell) {
color #2F3540
}
.action-btn {
width 100%
display flex
flex-wrap wrap
& > button {
margin 0
}
}
}
.tool-bar {
display flex
justify-content space-between
margin 0 0 20px 0
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<ElDialog v-model="showDialog"
:close-on-click-modal="false"
destroy-on-close
width="fit-content"
@close="dialogCloseHandler">
<ElForm ref="craftForm"
:model="formData"
:rules="rules"
class="form-panel"
label-width="auto">
<ElFormItem label="编码" prop="sn">
<ElInput
v-model="formData.sn"
:disabled="status === 'view'"
placeholder="编码"/>
</ElFormItem>
<ElFormItem label="产品" prop="goodsId">
<ADropTable v-model="formData.goodsId as string" :columns="dropTableColumns" :loader="dropTableLoader" :multiple="true" display-field="goodsName"/>
</ElFormItem>
<ElFormItem label="工艺名称" prop="craftName">
<ElInput
v-model="formData.craftName"
:disabled="status === 'view'"
placeholder="工艺名称"/>
</ElFormItem>
<ElFormItem label="工艺版本号" prop="craftVer">
<ElInput
v-model="formData.craftVer"
:disabled="status === 'view'"
placeholder="工艺版本号"/>
</ElFormItem>
<ElFormItem label="工艺类型" prop="craftCategory">
<ElSelect v-model="formData.craftCategory">
<ElOption label="自动化" value="ZiDongHua"/>
<ElOption label="人工" value="RenGong"/>
</ElSelect>
</ElFormItem>
<ElFormItem label="是否可用" prop="canuse">
<ElCheckbox
v-model="formData.canuse"
:disabled="status === 'view'"
placeholder="是否可用"/>
</ElFormItem>
<ElFormItem label="备注" prop="memo">
<ElInput
v-model="formData.memo"
:disabled="status === 'view'"
placeholder="备注"/>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
<ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</template>
</ElDialog>
</template>
<script lang="ts" setup>
import CraftApi from '@/pages/mfg/craft/craft-api.ts'
import Strings from '@/common/utils/strings.ts'
import FormUtil from '@/common/utils/formUtil.ts'
import Utils from '@/common/utils'
import {
ElMessage,
type FormInstance,
type FormRules,
} from 'element-plus'
import ADropTable from '@/components/a-drop-table/ADropTable.vue'
import GoodsApi from '@/pages/gds/goods/goods-api.ts'
const emits = defineEmits([ 'editSucc' ])
const showDialog = ref(false)
const submiting = ref(false)
const status = ref<'add' | 'view' | 'modify'>('add')
const craftFormIns = useTemplateRef<FormInstance>('craftForm')
const formData = Utils.resetAble(reactive<CraftTypes.SearchCraftResult>({}))
const rules = reactive<FormRules<CraftTypes.SearchCraftResult>>({
id: [ {required: true, message: '请填写Id', trigger: 'blur'} ],
sn: [ {required: true, message: '请填写编码', trigger: 'blur'} ],
goodsId: [ {required: true, message: '请填写终产品 Id', trigger: 'blur'} ],
craftName: [ {required: true, message: '请填写工艺名称', trigger: 'blur'} ],
craftVer: [ {required: true, message: '请填写工艺版本号', trigger: 'blur'} ],
craftCategory: [ {required: true, message: '请填写工艺类型字典编码craft_categoryZiDongHua-->自动化、RenGong-->人工', trigger: 'blur'} ],
canuse: [ {required: true, message: '请填写是否可用0-->否、1-->是', trigger: 'blur'} ],
memo: [ {required: true, message: '请填写备注', trigger: 'blur'} ],
})
const dropTableColumns = [
{
label: '商品编码',
prop: 'sn',
},
{
label: '产品名称',
prop: 'goodsName',
},
]
const dropTableLoader = (param: GoodsTypes.SearchGoodsParam) => {
return GoodsApi.paging(param).then(res => res.data)
}
function dialogCloseHandler() {
formData.$reset()
}
function submitHandler() {
if (status.value === 'view') return
submiting.value = true
if (formData.id != null) {
FormUtil.submit(craftFormIns, () => CraftApi.modify(formData))
.then(() => {
ElMessage.success('修改成功')
emits('editSucc')
showDialog.value = false
})
.finally(() => {
submiting.value = false
})
} else {
FormUtil.submit(craftFormIns, () => CraftApi.add(formData))
.then(() => {
ElMessage.success('添加成功')
emits('editSucc')
showDialog.value = false
})
.finally(() => {
submiting.value = false
})
}
}
defineExpose({
open(data: CraftTypes.SearchCraftResult = {}) {
showDialog.value = true
if (!Strings.isBlank(data.id)) {
status.value = 'modify'
CraftApi.detail(data.id!)
.then(res => {
formData.$reset(res.data)
})
} else {
status.value = 'add'
formData.$reset(data)
}
},
})
</script>
<style lang="stylus" scoped>
.form-panel {
padding 20px
}
</style>

View File

@ -0,0 +1,25 @@
import {
get,
post,
} from '@/common/utils/http-util.ts'
export default {
paging(data: CraftTypes.SearchCraftParam) {
return get<G.PageResult<CraftTypes.SearchCraftResult>>('/craft/paging', data)
},
detail(id: string) {
return get<CraftTypes.SearchCraftResult>('/craft/detail', {id})
},
add(data: CraftTypes.AddCraftParam) {
return post('/craft/add', data)
},
modify(data: CraftTypes.ModifyCraftParam) {
return post('/craft/modify', data)
},
del(ids: string[]) {
return post('/craft/del', ids)
},
disable(id: string, disable: boolean) {
return get('/craft/disable', {id, disable})
},
}

116
src/pages/mfg/craft/craft.d.ts vendored 100644
View File

@ -0,0 +1,116 @@
export {}
declare global {
namespace CraftTypes {
interface SearchCraftParam extends G.PageParam {
// Id
id?: string
// 编码
sn?: string
// 终产品 Id
goodsId?: string
// 工艺名称
craftName?: string
// 工艺版本号
craftVer?: string
// 工艺类型字典编码craft_categoryZiDongHua-->自动化、RenGong-->人工
craftCategory?: string
// 是否可用0-->否、1-->是
canuse?: boolean
// 备注
memo?: string
// 创建人 Id sys_user.id
creatorId?: string
// 修改人 Id sys_user.id
modifierId?: string
// 创建时间
createTime?: string
// 修改时间
modifyTime?: string
// 是否删除; 0-->未删除、1-->已删除
deleted?: boolean
}
interface SearchCraftResult {
// Id
id?: string
// 编码
sn?: string
// 终产品 Id
goodsId?: string
goodsName?: string
goodsSn?: string
// 工艺名称
craftName?: string
// 工艺版本号
craftVer?: string
// 工艺类型字典编码craft_categoryZiDongHua-->自动化、RenGong-->人工
craftCategory?: string
craftCategoryTxt?: string
// 是否可用0-->否、1-->是
canuse?: boolean
// 备注
memo?: string
// 创建时间
createTime?: string
}
interface AddCraftParam {
// Id
id?: string
// 编码
sn?: string
// 终产品 Id
goodsId?: string
// 工艺名称
craftName?: string
// 工艺版本号
craftVer?: string
// 工艺类型字典编码craft_categoryZiDongHua-->自动化、RenGong-->人工
craftCategory?: string
// 是否可用0-->否、1-->是
canuse?: boolean
// 备注
memo?: string
// 创建人 Id sys_user.id
creatorId?: string
// 修改人 Id sys_user.id
modifierId?: string
// 创建时间
createTime?: string
// 修改时间
modifyTime?: string
// 是否删除; 0-->未删除、1-->已删除
deleted?: boolean
}
interface ModifyCraftParam {
// Id
id?: string
// 编码
sn?: string
// 终产品 Id
goodsId?: string
// 工艺名称
craftName?: string
// 工艺版本号
craftVer?: string
// 工艺类型字典编码craft_categoryZiDongHua-->自动化、RenGong-->人工
craftCategory?: string
// 是否可用0-->否、1-->是
canuse?: boolean
// 备注
memo?: string
// 创建人 Id sys_user.id
creatorId?: string
// 修改人 Id sys_user.id
modifierId?: string
// 创建时间
createTime?: string
// 修改时间
modifyTime?: string
// 是否删除; 0-->未删除、1-->已删除
deleted?: boolean
}
}
}

View File

@ -0,0 +1,3 @@
export default {
component: () => import('@/pages/mfg/craft/Craft.vue'),
} as RouterTypes.RouteConfig

Binary file not shown.