Merge branch 'refs/heads/dev' into master_lz

# Conflicts:
#	src/views/system/user/index.vue
This commit is contained in:
2026-03-05 16:35:28 +08:00
11 changed files with 545 additions and 115 deletions

View File

@@ -25,6 +25,11 @@ export function socialLogin(req: any) {
return http.post<T.LoginResp>(`${BASE_URL}/login`, req) return http.post<T.LoginResp>(`${BASE_URL}/login`, req)
} }
/** @desc 三方账号登录 */
export function cardLogin(req: any) {
return http.post<T.LoginResp>(`${BASE_URL}/login`, req)
}
/** @desc 三方账号登录授权 */ /** @desc 三方账号登录授权 */
export function socialAuth(source: string) { export function socialAuth(source: string) {
return http.get<T.SocialAuthAuthorizeResp>(`${BASE_URL}/${source}`) return http.get<T.SocialAuthAuthorizeResp>(`${BASE_URL}/${source}`)

View File

@@ -42,13 +42,14 @@ export interface RouteItem {
} }
/** 认证类型 */ /** 认证类型 */
export type AuthType = 'ACCOUNT' | 'PHONE' | 'EMAIL' | 'SOCIAL' export type AuthType = 'ACCOUNT' | 'PHONE' | 'EMAIL' | 'SOCIAL' | 'CARD'
export const AuthTypeConstants = { export const AuthTypeConstants = {
ACCOUNT: 'ACCOUNT', ACCOUNT: 'ACCOUNT',
PHONE: 'PHONE', PHONE: 'PHONE',
EMAIL: 'EMAIL', EMAIL: 'EMAIL',
SOCIAL: 'SOCIAL', SOCIAL: 'SOCIAL',
CARD: 'CARD',
} as const } as const
/** 基础认证请求接口 */ /** 基础认证请求接口 */
@@ -77,6 +78,16 @@ export interface EmailLoginReq extends AuthReq {
captcha: string captcha: string
} }
/** 刷卡登录请求参数 */
export interface CardLoginReq extends AuthReq {
cardNumber: string
}
/** 邮箱登录请求参数 */
export interface CardLoginReq extends AuthReq {
card: string
}
/** 登录响应类型 */ /** 登录响应类型 */
export interface LoginResp { export interface LoginResp {
token: string token: string

View File

@@ -4,21 +4,21 @@ const BASE_URL = '/weighManage/material'
export interface WeighManageResp { export interface WeighManageResp {
id: string id: string
materialCode: string encoding: string
materialName: string materialName: string
materialSpec: string materialSpec: string
weight: number unitWeight: number
imageUrl: string photoUrl: string
matchResult: string matchResult: string
} }
export interface WeighManageQuery { export interface WeighManageQuery {
materialCode: string encoding: string
} }
/** @desc 查询物料信息 */ /** @desc 查询物料信息 */
export function getMaterialDetail(query: WeighManageQuery) { export function getMaterialDetail(code: string) {
return http.get<WeighManageResp>(`${BASE_URL}/detail`, query) return http.get<WeighManageResp>(`/admin/materialInfo/code/${code}`)
} }
/** @desc 新增人员管理 */ /** @desc 新增人员管理 */

View File

@@ -0,0 +1,60 @@
import http from '@/utils/http'
const BASE_URL = '/weighManage/workOrder'
export interface WorkOrderResp {
id: string
title: string
materialName: string
encoding: string
unitWeight: string
materialSpec: string
photoUrl: string
totalWeight: string
totalCount: string
createUserString: string
updateUserString: string
}
export interface WorkOrderInfoResp {
id: string
workOrderId: string
materialId: string
weightTime: string
quantity: string
weight: string
imgUrl: string
calculatedWeight: string
}
export interface WorkOrderQuery {
orderNo: string | undefined
materialName: string | undefined
encoding: string | undefined
userName: string | undefined
carNo: string | undefined
startDate: string | undefined
endDate: string | undefined
sort: Array<string>
}
export interface WorkOrderPageQuery extends WorkOrderQuery, PageQuery {}
/** @desc 查询工作订单列表 */
export function listWorkOrder(query: WorkOrderPageQuery) {
return http.get<PageRes<WorkOrderResp[]>>(`${BASE_URL}`, query)
}
/** @desc 查询工作订单详情 */
export function getWorkOrder(id: string) {
return http.get<Array<WorkOrderInfoResp>>(`${BASE_URL}/${id}`)
}
/** @desc 删除工作订单 */
export function deleteWorkOrder(ids: string | Array<string>) {
return http.del(`${BASE_URL}/${ids}`)
}
/** @desc 导出工作订单 */
export function exportWorkOrder(query: WorkOrderQuery) {
return http.download(`${BASE_URL}/export`, query)
}

View File

@@ -4,10 +4,12 @@ import { resetRouter } from '@/router'
import { import {
type AccountLoginReq, type AccountLoginReq,
AuthTypeConstants, AuthTypeConstants,
type CardLoginReq,
type EmailLoginReq, type EmailLoginReq,
type PhoneLoginReq, type PhoneLoginReq,
type UserInfo, type UserInfo,
accountLogin as accountLoginApi, accountLogin as accountLoginApi,
cardLogin as cardLoginApi,
emailLogin as emailLoginApi, emailLogin as emailLoginApi,
getUserInfo as getUserInfoApi, getUserInfo as getUserInfoApi,
logout as logoutApi, logout as logoutApi,
@@ -70,6 +72,13 @@ const storeSetup = () => {
token.value = res.data.token token.value = res.data.token
} }
// 刷卡登录
const cardLogin = async (req: CardLoginReq) => {
const res = await cardLoginApi({ ...req, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.CARD })
setToken(res.data.token)
token.value = res.data.token
}
// 三方账号登录 // 三方账号登录
const socialLogin = async (source: string, req: any) => { const socialLogin = async (source: string, req: any) => {
const res = await socialLoginApi({ ...req, source, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.SOCIAL }) const res = await socialLoginApi({ ...req, source, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.SOCIAL })
@@ -120,6 +129,7 @@ const storeSetup = () => {
accountLogin, accountLogin,
emailLogin, emailLogin,
phoneLogin, phoneLogin,
cardLogin,
socialLogin, socialLogin,
logout, logout,
logoutCallBack, logoutCallBack,

View File

@@ -18,6 +18,7 @@ declare module 'vue' {
ACheckboxGroup: typeof import('@arco-design/web-vue')['CheckboxGroup'] ACheckboxGroup: typeof import('@arco-design/web-vue')['CheckboxGroup']
ACol: typeof import('@arco-design/web-vue')['Col'] ACol: typeof import('@arco-design/web-vue')['Col']
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider'] AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
ADatePicker: typeof import('@arco-design/web-vue')['DatePicker']
ADescriptions: typeof import('@arco-design/web-vue')['Descriptions'] ADescriptions: typeof import('@arco-design/web-vue')['Descriptions']
ADescriptionsItem: typeof import('@arco-design/web-vue')['DescriptionsItem'] ADescriptionsItem: typeof import('@arco-design/web-vue')['DescriptionsItem']
ADivider: typeof import('@arco-design/web-vue')['Divider'] ADivider: typeof import('@arco-design/web-vue')['Divider']

View File

@@ -0,0 +1,118 @@
<template>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col-style="{ display: 'none' }"
:wrapper-col-style="{ flex: 1 }"
size="large"
@submit="handleLogin"
>
<a-form-item field="cardNumber" hide-label>
<a-input v-model="form.cardNumber" placeholder="请刷卡" allow-clear @keyup.enter="handleLogin" />
</a-form-item>
<a-form-item>
<a-space direction="vertical" fill class="w-full">
<a-button type="primary" :loading="loading" html-type="submit" size="large" long>立即登录</a-button>
</a-space>
</a-form-item>
</a-form>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { type FormInstance, Message } from '@arco-design/web-vue'
import { useTabsStore, useUserStore } from '@/stores'
const formRef = ref<FormInstance>()
const form = reactive({
cardNumber: '',
})
const rules: FormInstance['rules'] = {
cardNumber: [
{ required: true, message: '请刷卡或输入卡号' },
],
}
const userStore = useUserStore()
const tabsStore = useTabsStore()
const router = useRouter()
const loading = ref(false)
// 登录
const handleLogin = async () => {
const isInvalid = await formRef.value?.validate()
if (isInvalid) return
try {
loading.value = true
// 调用后端接口校验卡号
await userStore.cardLogin(form)
tabsStore.reset()
const { redirect, ...othersQuery } = router.currentRoute.value.query
await router.push({
path: (redirect as string) || '/',
query: {
...othersQuery,
},
})
Message.success('欢迎使用')
} catch (error) {
// 登录失败处理
} finally {
loading.value = false
}
}
// 监听键盘输入,实现自动识别卡号
onMounted(() => {
// 监听全局键盘事件,用于识别刷卡输入
document.addEventListener('keydown', (e) => {
// 刷卡器通常会快速输入卡号并以Enter键结束
if (e.key === 'Enter') {
// 遇到Enter键尝试登录
if (form.cardNumber.trim()) {
handleLogin()
}
} else if (e.key.length === 1 && !e.ctrlKey && !e.altKey && !e.metaKey) {
// 只捕获单个字符的按键,忽略控制键和功能键
form.cardNumber += e.key
} else if (e.key === 'Backspace') {
// 处理退格键
form.cardNumber = form.cardNumber.slice(0, -1)
}
})
})
</script>
<style scoped lang="scss">
.arco-input-wrapper,
:deep(.arco-select-view-single) {
height: 40px;
border-radius: 4px;
font-size: 13px;
}
.arco-input-wrapper.arco-input-error {
background-color: rgb(var(--danger-1));
border-color: rgb(var(--danger-3));
}
.arco-input-wrapper.arco-input-error:hover {
background-color: rgb(var(--danger-1));
border-color: rgb(var(--danger-6));
}
.arco-input-wrapper :deep(.arco-input) {
font-size: 13px;
color: var(--color-text-1);
}
.arco-input-wrapper:hover {
border-color: rgb(var(--arcoblue-6));
}
.btn {
height: 40px;
}
</style>

View File

@@ -15,28 +15,14 @@
<a-col :xs="24" :sm="12" :md="11"> <a-col :xs="24" :sm="12" :md="11">
<div class="login-right"> <div class="login-right">
<h3 v-if="isEmailLogin" class="login-right__title">邮箱登录</h3> <h3 v-if="isEmailLogin" class="login-right__title">邮箱登录</h3>
<EmailLogin v-if="isEmailLogin" />
<a-tabs v-else v-model:activeKey="activeTab" class="login-right__form"> <a-tabs v-else v-model:activeKey="activeTab" class="login-right__form">
<a-tab-pane key="1" title="账号登录"> <a-tab-pane key="1" title="账号登录">
<component :is="AccountLogin" v-if="activeTab === '1'" /> <component :is="AccountLogin" v-if="activeTab === '1'" />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" title="手机号登录"> <a-tab-pane key="2" title="刷卡">
<component :is="PhoneLogin" v-if="activeTab === '2'" /> <component :is="CardLogin" v-if="activeTab === '2'" />
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
<!-- <div class="login-right__oauth">
<a-divider orientation="center">其他登录方式</a-divider>
<div class="list">
<div v-if="isEmailLogin" class="mode item" @click="toggleLoginMode"><icon-user /> 账号/手机号登录</div>
<div v-else class="mode item" @click="toggleLoginMode"><icon-email /> 邮箱登录</div>
<a class="item" title="使用 Gitee 账号登录" @click="onOauth('gitee')">
<GiSvgIcon name="gitee" :size="24" />
</a>
<a class="item" title="使用 GitHub 账号登录" @click="onOauth('github')">
<GiSvgIcon name="github" :size="24" />
</a>
</div>
</div>-->
</div> </div>
</a-col> </a-col>
</a-row> </a-row>
@@ -51,51 +37,13 @@
<Background /> <Background />
</div> </div>
<div v-else class="login h5">
<div class="login-logo">
<img v-if="logo" :src="logo" alt="logo" />
<img v-else src="/logo.svg" alt="logo" />
<span>{{ title }}</span>
</div>
<a-row align="stretch" class="login-box">
<a-col :xs="24" :sm="12" :md="11">
<div class="login-right">
<h3 v-if="isEmailLogin" class="login-right__title">邮箱登录</h3>
<EmailLogin v-if="isEmailLogin" />
<a-tabs v-else v-model:activeKey="activeTab" class="login-right__form">
<a-tab-pane key="1" title="账号登录">
<component :is="AccountLogin" v-if="activeTab === '1'" />
</a-tab-pane>
<a-tab-pane key="2" title="手机号登录">
<component :is="PhoneLogin" v-if="activeTab === '2'" />
</a-tab-pane>
</a-tabs>
</div>
</a-col>
</a-row>
<div class="login-right__oauth">
<a-divider orientation="center">其他登录方式</a-divider>
<div class="list">
<div v-if="isEmailLogin" class="mode item" @click="toggleLoginMode"><icon-user /> 账号/手机号登录</div>
<div v-else class="mode item" @click="toggleLoginMode"><icon-email /> 邮箱登录</div>
<a class="item" title="使用 Gitee 账号登录" @click="onOauth('gitee')">
<GiSvgIcon name="gitee" :size="24" />
</a>
<a class="item" title="使用 GitHub 账号登录" @click="onOauth('github')">
<GiSvgIcon name="github" :size="24" />
</a>
</div>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import Background from './components/background/index.vue' import Background from './components/background/index.vue'
import AccountLogin from './components/account/index.vue' import AccountLogin from './components/account/index.vue'
import PhoneLogin from './components/phone/index.vue' import CardLogin from './components/card/index.vue'
import EmailLogin from './components/email/index.vue'
import { socialAuth } from '@/apis/auth'
import { useAppStore } from '@/stores' import { useAppStore } from '@/stores'
import { useDevice } from '@/hooks' import { useDevice } from '@/hooks'
@@ -109,16 +57,6 @@ const logo = computed(() => appStore.getLogo())
const isEmailLogin = ref(false) const isEmailLogin = ref(false)
const activeTab = ref('1') const activeTab = ref('1')
// 切换登录模式
const toggleLoginMode = () => {
isEmailLogin.value = !isEmailLogin.value
}
// 第三方登录授权
const onOauth = async (source: string) => {
const { data } = await socialAuth(source)
window.location.href = data.authorizeUrl
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -102,7 +102,7 @@ import { DisEnableStatusList } from '@/constant/common'
import { useDownload, useResetReactive, useTable } from '@/hooks' import { useDownload, useResetReactive, useTable } from '@/hooks'
import { isMobile } from '@/utils' import { isMobile } from '@/utils'
import has from '@/utils/has' import has from '@/utils/has'
import type { ColumnItem } from '@/components/GiForm' import type {ColumnItem} from "@/components/GiForm";
defineOptions({ name: 'SystemUser' }) defineOptions({ name: 'SystemUser' })
@@ -204,7 +204,7 @@ const reset = () => {
const onDelete = (record: UserResp) => { const onDelete = (record: UserResp) => {
return handleDelete(() => deleteUser(record.id), { return handleDelete(() => deleteUser(record.id), {
content: `是否确定删除用户「${record.username}」?`, content: `是否确定删除用户「${record.nickname}(${record.username})」?`,
showModal: true, showModal: true,
}) })
} }

View File

@@ -25,8 +25,8 @@
<div class="main-content"> <div class="main-content">
<!-- 左侧表单 --> <!-- 左侧表单 -->
<div class="left-section"> <div class="left-section">
<a-image v-if="!formData.imageUrl" :src="formData.imageUrl"/> <a-image v-if="!formData.photoUrl" :src="formData.photoUrl"/>
<img v-else :src="formData.imageUrl" class="sample-image square-image" alt="样图"> <img v-else :src="formData.photoUrl" class="sample-image square-image" alt="样图">
<a-form :model="formData" layout="vertical"> <a-form :model="formData" layout="vertical">
<div class="form-row"> <div class="form-row">
@@ -44,12 +44,12 @@
<div class="form-row"> <div class="form-row">
<div class="form-item"> <div class="form-item">
<a-form-item label="物料编码"> <a-form-item label="物料编码">
<a-input v-model="formData.materialCode" placeholder="物料编码" disabled /> <a-input v-model="formData.encoding" placeholder="物料编码" disabled />
</a-form-item> </a-form-item>
</div> </div>
<div class="form-item"> <div class="form-item">
<a-form-item label="重量"> <a-form-item label="重量">
<a-input v-model="formData.weight" placeholder="Kg" disabled /> <a-input v-model="formData.unitWeight" placeholder="Kg" disabled />
</a-form-item> </a-form-item>
</div> </div>
</div> </div>
@@ -83,7 +83,7 @@
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">物料编码:</span> <span class="label">物料编码:</span>
<span class="value">{{ formData.materialCode || '-' }}</span> <span class="value">{{ formData.encoding || '-' }}</span>
</div> </div>
</div> </div>
@@ -123,19 +123,19 @@
<div class="form-row"> <div class="form-row">
<div class="form-item"> <div class="form-item">
<label>输入数量:</label> <label>输入数量:</label>
<a-input v-model="inputQuantity" placeholder="请输入数量" /> <a-input v-model="inputQuantity" placeholder="请输入数量" @change="calculateWeight" />
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>计算重量:</label>
<a-input v-model="calculatedWeight" placeholder="-" disabled />
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<div class="form-item"> <div class="form-item">
<label>对应重量:</label> <label>对应重量:</label>
<a-input v-model="inputWeight" placeholder="请输入重量" /> <a-input v-model="inputWeight" placeholder="-" disabled/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>称重次数:</label>
<a-input v-model="weighingCount" placeholder="称重次数" disabled />
</div> </div>
</div> </div>
<div class="image-placeholder large-image">实时画面</div> <div class="image-placeholder large-image">实时画面</div>
@@ -185,7 +185,7 @@
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">物料编码:</span> <span class="label">物料编码:</span>
<span class="value">{{ formData.materialCode }}</span> <span class="value">{{ formData.encoding }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">物料规格:</span> <span class="label">物料规格:</span>
@@ -193,7 +193,7 @@
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">重量:</span> <span class="label">重量:</span>
<span class="value">{{ formData.weight }}</span> <span class="value">{{ formData.unitWeight }}</span>
</div> </div>
</div> </div>
<div class="completion-actions"> <div class="completion-actions">
@@ -215,6 +215,7 @@
v-if="activeStep < 3" v-if="activeStep < 3"
type="primary" type="primary"
@click="handleNext" @click="handleNext"
:disabled="activeStep === 1 && formData.matchResult !== 'success'"
class="next-button" class="next-button"
> >
{{ activeStep === 2 ? '完成' : '下一步' }} {{ activeStep === 2 ? '完成' : '下一步' }}
@@ -226,8 +227,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed } from 'vue' import { ref, reactive, computed } from 'vue'
import { Modal, Icon } from '@arco-design/web-vue' import { Modal } from '@arco-design/web-vue'
import {getMaterialDetail, type WeighManageQuery} from "@/apis/weightManage/weightManage"; import { getMaterialDetail } from "@/apis/weightManage/weightManage";
// 当前步骤 // 当前步骤
const activeStep = ref(1) const activeStep = ref(1)
@@ -235,17 +236,18 @@ const activeStep = ref(1)
// 表单数据 // 表单数据
const formData = reactive({ const formData = reactive({
inputMaterialCode: '', // 输入的物料编码 inputMaterialCode: '', // 输入的物料编码
materialCode: '', // 物料编码 encoding: '', // 物料编码
materialName: '', // 物料名称 materialName: '', // 物料名称
materialSpec: '', // 物料规格 materialSpec: '', // 物料规格
weight: 0, // 重量 unitWeight: 0, // 重量
imageUrl: '', // 样图URL photoUrl: '', // 样图URL
matchResult: '' // 比对结果 matchResult: '' // 比对结果
}) })
// 称重登记页面数据 // 称重登记页面数据
const inputQuantity = ref('') const inputQuantity = ref('')
const inputWeight = ref('') const inputWeight = ref('')
const calculatedWeight = ref('')
const weighingCount = ref(1) const weighingCount = ref(1)
// 称重列表数据 // 称重列表数据
@@ -257,7 +259,7 @@ const weighingList = ref([
const taskId = computed(() => { const taskId = computed(() => {
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '') const date = new Date().toISOString().slice(0, 10).replace(/-/g, '')
const random = Math.floor(1000 + Math.random() * 9000) const random = Math.floor(1000 + Math.random() * 9000)
return `${date}${formData.materialCode}${random}` return `${date}${formData.encoding}${random}`
}) })
// 称重表格列 // 称重表格列
@@ -279,6 +281,12 @@ const columns = [
key: 'weight', key: 'weight',
className: 'green-bg' className: 'green-bg'
}, },
{
title: '计算重量',
dataIndex: 'calculatedWeight',
key: 'calculatedWeight',
className: 'green-bg'
},
{ {
title: '抓拍图片', title: '抓拍图片',
dataIndex: 'image', dataIndex: 'image',
@@ -304,29 +312,25 @@ const handleMaterialCodeChange = async () => {
console.error('获取物料数据失败:', error) console.error('获取物料数据失败:', error)
} }
} else { } else {
formData.materialCode = ""; formData.encoding = "";
formData.materialName = ""; formData.materialName = "";
formData.materialSpec = ""; formData.materialSpec = "";
formData.weight = 0; formData.unitWeight = 0;
formData.imageUrl = ""; formData.photoUrl = "";
formData.matchResult = ""; formData.matchResult = "";
} }
}; };
// 模拟调用后端接口 // 模拟调用后端接口
const fetchMaterialData = async (code: string) => { const fetchMaterialData = async (code: string) => {
const query: WeighManageQuery = { getMaterialDetail(code).then(res => {
materialCode: code
}
getMaterialDetail(query).then(res => {
if (res.code == '0') { if (res.code == '0') {
// 更新表单数据 // 更新表单数据
formData.materialCode = res.data.materialCode formData.encoding = res.data.encoding
formData.materialName = res.data.materialName formData.materialName = res.data.materialName
formData.materialSpec = res.data.materialSpec formData.materialSpec = res.data.materialSpec
formData.weight = res.data.weight formData.unitWeight = res.data.unitWeight
// formData.imageUrl = res.data.imageUrl formData.photoUrl = res.data.photoUrl
formData.imageUrl = "https://menjing.hzjj.cn/uploadPath/2024/01/20/XMT-Q3_20240120171349A257.jpg"
// 假设后端返回比对结果 // 假设后端返回比对结果
// formData.matchResult = res.data.matchResult || 'failed' // 这里根据实际接口返回调整 // formData.matchResult = res.data.matchResult || 'failed' // 这里根据实际接口返回调整
formData.matchResult = res.data.matchResult || 'success' // 这里根据实际接口返回调整 formData.matchResult = res.data.matchResult || 'success' // 这里根据实际接口返回调整
@@ -365,6 +369,19 @@ const handleBackToFirst = () => {
activeStep.value = 1 activeStep.value = 1
} }
// 计算重量
const calculateWeight = () => {
if (inputQuantity.value && formData.unitWeight) {
const singleWeight = parseFloat(formData.unitWeight.toString())
const quantity = parseFloat(inputQuantity.value)
if (!isNaN(singleWeight) && !isNaN(quantity)) {
calculatedWeight.value = (singleWeight * quantity).toFixed(2) + 'g'
}
} else {
calculatedWeight.value = ''
}
}
// 处理确定 // 处理确定
@@ -375,7 +392,8 @@ const handleConfirm = () => {
count: weighingCount.value, count: weighingCount.value,
name: formData.materialName, name: formData.materialName,
quantity: inputQuantity.value, quantity: inputQuantity.value,
weight: inputWeight.value, unitWeight: inputWeight.value,
calculatedWeight: calculatedWeight.value,
image: '图片' image: '图片'
} }
// 添加到列表 // 添加到列表
@@ -383,6 +401,7 @@ const handleConfirm = () => {
// 重置输入 // 重置输入
inputQuantity.value = '' inputQuantity.value = ''
inputWeight.value = '' inputWeight.value = ''
calculatedWeight.value = ''
// 称重次数累加 // 称重次数累加
weighingCount.value = weighingList.value.length + 1 weighingCount.value = weighingList.value.length + 1
} }
@@ -391,6 +410,7 @@ const handleConfirm = () => {
const handleReset = () => { const handleReset = () => {
inputQuantity.value = '' inputQuantity.value = ''
inputWeight.value = '' inputWeight.value = ''
calculatedWeight.value = ''
} }
// 处理删除 // 处理删除

View File

@@ -0,0 +1,267 @@
<template>
<div class="gi_table_page">
<GiTable
row-key="id"
:data="dataList"
:columns="columns"
:loading="loading"
:scroll="{ x: '100%', y: '100%', minWidth: 1600 }"
:pagination="pagination"
:disabled-tools="['size']"
:disabled-column-keys="['name']"
:selected-keys="selectedKeys"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
@select-all="selectAll"
@select="select"
@refresh="search"
>
<template #toolbar-left>
<a-input-search v-model="queryForm.userName" placeholder="请输入姓名" allow-clear @search="search" />
<a-input-search v-model="queryForm.carNo" placeholder="请输入人员卡号" allow-clear @search="search" />
<a-input-search v-model="queryForm.orderNo" placeholder="请输入订单号" allow-clear @search="search" />
<a-input-search v-model="queryForm.materialName" placeholder="请输入物料名称" allow-clear @search="search" />
<a-input-search v-model="queryForm.encoding" placeholder="请输入物料编码" allow-clear @search="search" />
<a-date-picker
v-model="queryForm.startDate"
placeholder="请选择开始时间"
show-time
format="YYYY-MM-DD HH:mm:ss"
style="height: 32px"
@change="search"
/>
<a-date-picker
v-model="queryForm.endDate"
placeholder="请选择结束时间"
show-time
format="YYYY-MM-DD HH:mm:ss"
style="height: 32px"
@change="search"
/>
<a-button @click="reset">
<template #icon><icon-refresh /></template>
<template #default>重置</template>
</a-button>
</template>
<template #toolbar-right>
<a-button v-permission="['workOrder:record:delete']" type="outline" status="danger" @click="onDelete">
<template #icon><icon-delete /></template>
<template #default>删除</template>
</a-button>
<a-button v-permission="['workOrder:record:export']" @click="onExport">
<template #icon><icon-download /></template>
<template #default>导出</template>
</a-button>
</template>
<template #photoUrl="{ record }">
<a-image :src="record.photoUrl" width="55" />
</template>
<template #unitWeight="{ record }">
{{ record.unitWeight + 'g' }}
</template>
<template #totalWeight="{ record }">
{{ record.totalWeight + 'g' }}
</template>
<template #action="{ record }">
<a-space>
<a-link v-permission="['workOrder:record:detail']" title="详情" @click="onDetail(record)">详情</a-link>
<a-link v-permission="['workOrder:record:delete']" status="danger" :title="'删除'" @click="onDeleteOne(record.id)">删除</a-link>
</a-space>
</template>
</GiTable>
<!-- 详情弹窗 -->
<a-modal
v-model:visible="detailModalVisible"
title="称重详情"
width="800px"
:loading="detailLoading"
>
<GiTable
v-if="detailData.length > 0"
row-key="id"
:data="detailData"
:columns="detailColumns"
:scroll="{ x: '100%', y: '100%', minWidth: 700 }"
>
<template #imgUrl="{ record }">
<a-image :src="record.imgUrl" width="50" />
</template>
<template #calculatedWeight="{ record }">
{{ record.calculatedWeight + 'g' }}
</template>
<template #weight="{ record }">
{{ record.weight + 'g' }}
</template>
</GiTable>
<div v-else class="no-data">
<icon-loading style="font-size: 48px; color: rgb(var(--arcoblue-6));" />
<p>暂无称重数据</p>
</div>
</a-modal>
</div>
</template>
<script setup lang="ts">
import type { TableInstanceColumns } from '@/components/GiTable/type'
import { useDownload, useTable } from '@/hooks'
import { isMobile } from '@/utils'
import has from '@/utils/has'
import type GiTable from "@/components/GiTable/index.vue";
import {ref, reactive} from "vue";
import {Message} from "@arco-design/web-vue";
import {
deleteWorkOrder,
exportWorkOrder, getWorkOrder,
listWorkOrder,
type WorkOrderQuery,
type WorkOrderResp
} from "@/apis/workOrder/workOrder";
defineOptions({ name: 'Record' })
const queryForm = reactive<WorkOrderQuery>({
orderNo: undefined,
materialName: undefined,
encoding: undefined,
userName: undefined,
carNo: undefined,
startDate: undefined,
endDate: undefined,
sort: ['w.id,desc']
})
const {
tableData: dataList,
loading,
pagination,
selectedKeys,
select,
selectAll,
search,
handleDelete,
} = useTable((page) => listWorkOrder({ ...queryForm, ...page }), { immediate: true })
// 创建工具函数统一处理列配置
const processColumns = (columns: TableInstanceColumns[]): TableInstanceColumns[] => {
return columns.map(column => {
const defaultConfig = {
ellipsis: true,
tooltip: true
};
return { ...defaultConfig, ...column };
});
};
// 定义列配置,使用工具函数处理
const columns = ref<TableInstanceColumns[]>(processColumns([
{ title: '创建人', dataIndex: 'createUserString' },
{ title: '卡号', dataIndex: 'cardNo' },
{ title: '标题', dataIndex: 'title', width: 180 },
{ title: '物料图片', dataIndex: 'photoUrl', slotName: 'photoUrl', minWidth: 180, ellipsis: true, tooltip: true },
{ title: '物料名称', dataIndex: 'materialName' },
{ title: '物料编码', dataIndex: 'encoding' },
{ title: '单位克重', dataIndex: 'unitWeight' ,slotName: 'unitWeight'},
{ title: '总重量', dataIndex: 'totalWeight' ,slotName: 'totalWeight'},
{ title: '称重次数', dataIndex: 'totalCount' },
{ title: '创建时间', dataIndex: 'createTime', width: 180 },
{
title: '操作',
dataIndex: 'action',
slotName: 'action',
width: 160,
align: 'center',
fixed: !isMobile() ? 'right' : undefined,
show: has.hasPermOr(['workOrder:record:detail', 'workOrder:record:update', 'workOrder:record:delete'])
}
]))
// 重置
const reset = () => {
queryForm.orderNo = undefined
queryForm.materialName = undefined
queryForm.encoding = undefined
queryForm.userName = undefined
queryForm.carNo = undefined
queryForm.startDate = undefined
queryForm.endDate = undefined
search()
}
// 详情弹窗状态
const detailModalVisible = ref(false)
const detailLoading = ref(false)
const detailData = ref<any[]>([])
// 详情列配置
const detailColumns = ref<TableInstanceColumns[]>([
{ title: '称重次数', dataIndex: 'weightTime' },
{ title: '物料名称', dataIndex: 'materialName' },
{ title: '数量', dataIndex: 'quantity' },
{ title: '重量', dataIndex: 'weight', slotName: 'weight' },
{ title: '计算重量', dataIndex: 'calculatedWeight', slotName: 'calculatedWeight' },
{ title: '抓拍图片', dataIndex: 'imgUrl', slotName: 'imgUrl' }
])
// 详情
const onDetail = async (record: WorkOrderResp) => {
detailLoading.value = true
detailModalVisible.value = true
detailData.value = []
getWorkOrder(record.id).then(res => {
if (res.code == '0') {
detailData.value = res.data;
detailLoading.value = false
} else {
Message.error('获取详情失败')
}
});
};
// 删除
const onDeleteOne = (id) => {
return handleDelete(() => deleteWorkOrder(id), {
content: `是否确定删除该条数据?`,
showModal: true
})
}
// 删除
const onDelete = () => {
if (!selectedKeys.value.length) {
return Message.warning('请选择数据')
}
return handleDelete(() => deleteWorkOrder(selectedKeys.value), {
content: `是否确定删除选中的 ${selectedKeys.value.length} 条数据?`,
showModal: true
})
}
// 导出
const onExport = () => {
useDownload(() => exportWorkOrder(queryForm))
}
</script>
<style scoped lang="scss">
.no-data {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 0;
color: var(--color-text-4);
p {
margin-top: 16px;
font-size: 14px;
}
}
</style>