焊接首页
@@ -3,5 +3,13 @@ import {
|
||||
post
|
||||
} from '@/utils/request.js'
|
||||
|
||||
|
||||
/**
|
||||
* 登录接口
|
||||
*/
|
||||
export const reqLogin = (data) => post('user/postLoginOn', data)
|
||||
|
||||
/**
|
||||
* 请求控制权限
|
||||
*/
|
||||
|
||||
export const reqMenuPowerList = (userId,projectId)=>get(`User/getMenuPowerList?userId=${userId}&projectId=${projectId}`)
|
||||
@@ -0,0 +1,11 @@
|
||||
import {
|
||||
get,
|
||||
post
|
||||
} from '@/utils/request.js'
|
||||
|
||||
// BaseInfo/getProjectWorkArea?projectId=0792b983-4de2-44d5-adba-361b7fc99a97&
|
||||
|
||||
/**
|
||||
* 请求单位工程
|
||||
*/
|
||||
export const reqProjectWorkArea = (projectId)=>get(`BaseInfo/getProjectWorkArea?projectId=${projectId}`)
|
||||
@@ -0,0 +1,9 @@
|
||||
import {
|
||||
get,
|
||||
post
|
||||
} from '@/utils/request.js'
|
||||
|
||||
/**
|
||||
* 获取焊接首页数据
|
||||
*/
|
||||
export const reqHJIndexData = (projectId)=>get(`HJGLIndex/GetJGLIndexItem?projectId=${projectId}`)
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
// 首页
|
||||
.home {
|
||||
padding-top: 80px;
|
||||
padding-bottom: 60rpx;
|
||||
.u-border-bottom:after {
|
||||
border: none;
|
||||
}
|
||||
@@ -40,7 +42,9 @@
|
||||
}
|
||||
|
||||
&-icons {
|
||||
height: 160rpx;
|
||||
position: fixed;
|
||||
top: 95px;
|
||||
height: 80px;
|
||||
width: 100%;
|
||||
background-color: $u-type-primary;
|
||||
display: flex;
|
||||
@@ -145,4 +149,206 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 快捷应用 & 业务查询公共样式
|
||||
.section-card {
|
||||
margin: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
||||
padding: 24rpx;
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
|
||||
.title-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-line {
|
||||
display: inline-block;
|
||||
width: 8rpx;
|
||||
height: 28rpx;
|
||||
background: $u-type-primary;
|
||||
border-radius: 4rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
padding: 8rpx;
|
||||
opacity: 0.8;
|
||||
&:active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 24rpx 16rpx;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16rpx 8rpx;
|
||||
border-radius: 12rpx;
|
||||
transition: background 0.2s;
|
||||
min-width: 0;
|
||||
|
||||
&:active {
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
margin-top: 12rpx;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑快捷应用弹窗样式
|
||||
.edit-quick {
|
||||
padding: 32rpx 32rpx 0 32rpx;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
|
||||
.edit-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
padding-bottom: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.edit-tip {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-grid {
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, calc(25% - 20rpx));
|
||||
gap: 20rpx;
|
||||
padding: 24rpx 0;
|
||||
min-height: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.edit-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 160rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 16rpx;
|
||||
position: relative;
|
||||
transition: all 0.25s;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.active {
|
||||
background: rgba($color: #4a7df5, $alpha: 0.08);
|
||||
border: 2rpx solid $u-type-primary;
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.edit-label {
|
||||
margin-top: 14rpx;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.selected-mark {
|
||||
position: absolute;
|
||||
top: 6rpx;
|
||||
right: 6rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
background: $u-type-primary;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba($color: $u-type-primary, $alpha: 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.edit-btns {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding-top: 20rpx;
|
||||
padding-bottom: 10rpx;
|
||||
position: absolute;
|
||||
bottom: 10px;left:0;right:0;
|
||||
.u-size-medium {
|
||||
width: 40%;
|
||||
}
|
||||
.btn-cancel,
|
||||
.btn-confirm {
|
||||
height: 88rpx;
|
||||
font-size: 30rpx;
|
||||
border: none;
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: #f5f6f8;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
box-shadow: 0 4rpx 16rpx rgba($color: $u-type-primary, $alpha: 0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "nbd-icon"; /* Project id 5193086 */
|
||||
src: url('//at.alicdn.com/t/c/font_5193086_zgg3m1cv22e.woff2?t=1781003671405') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_5193086_zgg3m1cv22e.woff?t=1781003671405') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_5193086_zgg3m1cv22e.ttf?t=1781003671405') format('truetype');
|
||||
src: url('//at.alicdn.com/t/c/font_5193086_dyc00qt3bz.woff2?t=1781056075220') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_5193086_dyc00qt3bz.woff?t=1781056075220') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_5193086_dyc00qt3bz.ttf?t=1781056075220') format('truetype');
|
||||
}
|
||||
|
||||
.nbd-icon {
|
||||
@@ -13,6 +13,26 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.nbd-icon-a-facheguanli:before {
|
||||
content: "\e683";
|
||||
}
|
||||
|
||||
.nbd-icon-yuzhijian:before {
|
||||
content: "\e647";
|
||||
}
|
||||
|
||||
.nbd-icon-baozhuangguanli:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.nbd-icon-diankouguanli:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.nbd-icon-shiyabao:before {
|
||||
content: "\e657";
|
||||
}
|
||||
|
||||
.nbd-icon-daiban:before {
|
||||
content: "\e612";
|
||||
}
|
||||
|
||||
@@ -12,9 +12,13 @@
|
||||
</view>
|
||||
<scroll-view scroll-y="true" class="project-select-inner">
|
||||
<view class="project-select-inner-wrap">
|
||||
<view class="li" v-for="(item,idx) in props.list" :key="idx"
|
||||
:class="selectObj[props.valueKey] == item[props.valueKey]?'active':''" @click="handerChangList(item)">{{item[props.labelKey]}}
|
||||
<view class="li" v-for="(item,idx) in filteredList" :key="idx"
|
||||
:class="selectObj && selectObj[props.valueKey] == item[props.valueKey] ? 'active' : ''"
|
||||
@click="handerChangList(item)"
|
||||
>
|
||||
{{ item[props.labelKey] }}
|
||||
</view>
|
||||
<view v-if="filteredList.length === 0" class="empty-tip">暂无匹配项目</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
@@ -24,7 +28,8 @@
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
watch
|
||||
watch,
|
||||
computed
|
||||
} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -61,6 +66,14 @@
|
||||
|
||||
const selectObj = ref(null)
|
||||
|
||||
// 过滤后的列表
|
||||
const filteredList = computed(() => {
|
||||
if (!props.searchKey) return props.list
|
||||
return props.list.filter(item =>
|
||||
item[props.labelKey]?.includes(props.searchKey)
|
||||
)
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
visible.value = val
|
||||
if(val){
|
||||
@@ -68,7 +81,7 @@
|
||||
if (selectedItem) {
|
||||
selectObj.value = selectedItem
|
||||
} else {
|
||||
selectObj.value = {}
|
||||
selectObj.value = null
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -92,3 +105,59 @@
|
||||
emit('searchChange', val)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.project-select {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
|
||||
.project-select-btns {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.project-select-filter {
|
||||
padding: 16rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.project-select-inner {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
.project-select-inner-wrap {
|
||||
padding: 0 32rpx 32rpx;
|
||||
|
||||
.li {
|
||||
padding: 28rpx 0;
|
||||
border-bottom: 1rpx solid #f5f6f8;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $u-type-primary;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
padding: 80rpx 0;
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,325 @@
|
||||
<template>
|
||||
<u-popup v-model="localVisible" mode="bottom" :height="height">
|
||||
<view class="nbd-select">
|
||||
<!-- 标题栏 -->
|
||||
<view class="nbd-select-header" v-if="title">
|
||||
<text class="nbd-select-title">{{ title }}</text>
|
||||
<text class="nbd-select-tip" v-if="tip">{{ tip }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="nbd-select-filter" v-if="showSearch">
|
||||
<u-search
|
||||
:placeholder="searchPlaceholder"
|
||||
:height="72"
|
||||
v-model="searchKey"
|
||||
:show-action="false"
|
||||
@change="handleSearch"
|
||||
@clear="handleSearch"
|
||||
></u-search>
|
||||
</view>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<scroll-view scroll-y="true" class="nbd-select-inner">
|
||||
<view class="nbd-select-inner-wrap">
|
||||
<!-- 空状态 -->
|
||||
<view class="nbd-select-empty" v-if="filteredList.length === 0">
|
||||
<u-empty mode="list" text="暂无数据"></u-empty>
|
||||
</view>
|
||||
|
||||
<!-- 列表项 -->
|
||||
<view
|
||||
v-for="(item, idx) in filteredList"
|
||||
:key="idx"
|
||||
class="nbd-select-item"
|
||||
:class="{ active: isSelected(item) }"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<text class="nbd-select-item-text">{{ item[labelKey] }}</text>
|
||||
<view v-if="multiple" class="nbd-select-checkbox">
|
||||
<u-icon
|
||||
:name="isSelected(item) ? 'checkbox-mark' : 'square'"
|
||||
:size="36"
|
||||
:color="isSelected(item) ? $u.type.primary : '#dcdee0'"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="nbd-select-footer">
|
||||
<u-button class="nbd-select-btn-cancel" @click="handleCancel">
|
||||
{{ cancelText }}
|
||||
</u-button>
|
||||
<u-button class="nbd-select-btn-confirm" type="primary" @click="handleConfirm">
|
||||
{{ confirmText }}
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
// 弹窗显示状态
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹窗高度
|
||||
height: {
|
||||
type: String,
|
||||
default: '70%'
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题提示
|
||||
tip: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 数据列表
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 选中项(支持数组,多选时使用)
|
||||
model: {
|
||||
type: [Object, Array, String, Number],
|
||||
default: null
|
||||
},
|
||||
// 是否多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示搜索框
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 搜索占位符
|
||||
searchPlaceholder: {
|
||||
type: String,
|
||||
default: '请输入关键词搜索'
|
||||
},
|
||||
// 字段名配置
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
// 自定义搜索字段(默认搜索 labelKey)
|
||||
searchKeyField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 按钮文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确定'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'update:model',
|
||||
'cancel',
|
||||
'confirm',
|
||||
'searchChange',
|
||||
'change'
|
||||
])
|
||||
|
||||
const localVisible = ref(false)
|
||||
const searchKey = ref('')
|
||||
|
||||
// 当前选中的项(多选时为数组)
|
||||
const selectedItems = ref(props.multiple ? (props.model || []) : (props.model || null))
|
||||
|
||||
// 监听外部 model 变化
|
||||
watch(() => props.model, (newVal) => {
|
||||
selectedItems.value = props.multiple ? (newVal || []) : newVal
|
||||
}, { deep: true })
|
||||
|
||||
// 弹窗状态同步
|
||||
watch(() => props.modelValue, (val) => {
|
||||
localVisible.value = val
|
||||
if (val) {
|
||||
// 打开时重置搜索
|
||||
searchKey.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
watch(localVisible, (val) => {
|
||||
emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
// 过滤后的列表
|
||||
const filteredList = computed(() => {
|
||||
if (!searchKey.value) return props.list
|
||||
|
||||
const keyword = searchKey.value.toLowerCase()
|
||||
const searchField = props.searchKeyField || props.labelKey
|
||||
|
||||
return props.list.filter(item => {
|
||||
const value = item[searchField]
|
||||
if (value === null || value === undefined) return false
|
||||
return String(value).toLowerCase().includes(keyword)
|
||||
})
|
||||
})
|
||||
|
||||
// 判断是否选中
|
||||
const isSelected = (item) => {
|
||||
if (props.multiple) {
|
||||
return selectedItems.value.some(
|
||||
selected => selected[props.valueKey] === item[props.valueKey]
|
||||
)
|
||||
}
|
||||
if (selectedItems.value && typeof selectedItems.value === 'object') {
|
||||
return selectedItems.value[props.valueKey] === item[props.valueKey]
|
||||
}
|
||||
return selectedItems.value === item[props.valueKey]
|
||||
}
|
||||
|
||||
// 点击列表项
|
||||
const handleItemClick = (item) => {
|
||||
if (props.multiple) {
|
||||
const index = selectedItems.value.findIndex(
|
||||
selected => selected[props.valueKey] === item[props.valueKey]
|
||||
)
|
||||
if (index > -1) {
|
||||
selectedItems.value.splice(index, 1)
|
||||
} else {
|
||||
selectedItems.value.push(item)
|
||||
}
|
||||
} else {
|
||||
selectedItems.value = item
|
||||
}
|
||||
emit('change', selectedItems.value)
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = (val) => {
|
||||
emit('searchChange', val)
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
emit('cancel')
|
||||
localVisible.value = false
|
||||
}
|
||||
|
||||
// 确认
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', selectedItems.value)
|
||||
emit('update:model', selectedItems.value)
|
||||
localVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nbd-select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24rpx 32rpx 20rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.nbd-select-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.nbd-select-tip {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&-filter {
|
||||
padding: 16rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
&-inner {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0 32rpx;
|
||||
|
||||
&-wrap {
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-empty {
|
||||
padding: 80rpx 0;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx 0;
|
||||
border-bottom: 1rpx solid #f5f6f8;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.active .nbd-select-item-text {
|
||||
color: $u-type-primary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nbd-select-item-text {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nbd-select-checkbox {
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&-footer {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 20rpx 32rpx 40rpx;
|
||||
|
||||
.nbd-select-btn-cancel,
|
||||
.nbd-select-btn-confirm {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,8 +20,47 @@
|
||||
"navigationBarTitleText": "首页",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的"
|
||||
}
|
||||
}
|
||||
],
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "pipe",
|
||||
"pages": [
|
||||
{
|
||||
"path": "diankou/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "点口管理"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#cbcbcb",
|
||||
"backgroundColor": "#2979ff",
|
||||
"selectedColor": "#ffffff",
|
||||
"borderStyle": "black",
|
||||
"list": [{
|
||||
"text": "首页",
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "/static/tabs/home.png",
|
||||
"selectedIconPath": "/static/tabs/home-active.png"
|
||||
},
|
||||
{
|
||||
"text": "个人中心",
|
||||
"pagePath": "pages/mine/index",
|
||||
"iconPath": "static/tabs/mine.png",
|
||||
"selectedIconPath": "static/tabs/mine-active.png"
|
||||
}
|
||||
],
|
||||
"position": "bottom"
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTitleText": "施工管理",
|
||||
|
||||
@@ -18,48 +18,112 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="home-hj">
|
||||
<view class="hj-title">项目管道实时预制率</view>
|
||||
<view class="hj-content">
|
||||
<view class="hj-row">
|
||||
<view class="hj-cell">
|
||||
<text class="hj-label">工厂焊接一次合格率</text>
|
||||
<text class="hj-value">99.5%</text>
|
||||
<text class="hj-value">{{hjIndexData?.GCFirstPassRate}}</text>
|
||||
</view>
|
||||
<view class="hj-cell">
|
||||
<text class="hj-label">现场焊接一次合格率</text>
|
||||
<text class="hj-value">0%</text>
|
||||
<text class="hj-value">{{hjIndexData?.XCFirstPassRate}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="hj-row">
|
||||
<view class="hj-cell">
|
||||
<text class="hj-label">工厂预制进度</text>
|
||||
<text class="hj-value hj-sub">49229.500/286639.000 DIN</text>
|
||||
<text class="hj-value hj-sub">{{hjIndexData?.GCProgress}}</text>
|
||||
</view>
|
||||
<view class="hj-cell">
|
||||
<text class="hj-label">现场焊接进度</text>
|
||||
<text class="hj-value hj-sub">5906.500/84951.000 DIN</text>
|
||||
<text class="hj-value hj-sub">{{hjIndexData?.XCProgress}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="hj-row">
|
||||
<view class="hj-cell">
|
||||
<text class="hj-label">工厂焊工功效</text>
|
||||
<text class="hj-value">252 DIN/天</text>
|
||||
<text class="hj-value">{{hjIndexData?.GCWelderEfficacy}}</text>
|
||||
</view>
|
||||
<view class="hj-cell">
|
||||
<text class="hj-label">现场焊接功效</text>
|
||||
<text class="hj-value">95 DIN/天</text>
|
||||
<text class="hj-value">{{hjIndexData?.XCWelderEfficacy}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="hj-footer">
|
||||
<text class="hj-footer-label">项目管道实时预制率 (包含工厂和现场的预制口)</text>
|
||||
<text class="hj-footer-value">13%</text>
|
||||
<text class="hj-footer-value">{{hjIndexData?.PipePrefabricationRate}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快捷应用 -->
|
||||
<view class="section-card home-quick">
|
||||
<view class="section-title">
|
||||
<view class="title-left">
|
||||
<text class="title-line"></text>
|
||||
<text>快捷应用</text>
|
||||
</view>
|
||||
<u-icon class="edit-btn" name="edit-pen" :size="32" color="#999" @click="openEditQuick" />
|
||||
</view>
|
||||
<view class="section-grid">
|
||||
<view class="grid-item" v-for="(item, idx) in quickApps" :key="idx" @click="handleSkip(item)">
|
||||
<view class="icon-box">
|
||||
<u-icon :name="item.icon" :size="56" :custom-prefix='item.prefix'
|
||||
:color="pastelColors[idx % 6]" />
|
||||
</view>
|
||||
<text class="item-label">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<!-- 业务查询 -->
|
||||
<view class="section-card home-query" v-if="queryApps&&queryApps.length>0">
|
||||
<view class="section-title">
|
||||
<view class="title-left">
|
||||
<text class="title-line"></text>
|
||||
<text>业务查询</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="section-grid">
|
||||
<view class="grid-item" v-for="(item, idx) in queryApps" :key="idx" @click="handleSkip(item)">
|
||||
<view class="icon-box">
|
||||
<u-icon :name="item.icon" :custom-prefix='item.prefix' :size="56" :color="item.color" />
|
||||
</view>
|
||||
<text class="item-label">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 编辑快捷应用弹窗 -->
|
||||
<u-popup v-model="showEditQuick" mode="bottom" height="70%">
|
||||
<view class="edit-quick">
|
||||
<view class="edit-title">
|
||||
<text>选择快捷应用</text>
|
||||
</view>
|
||||
<view class="edit-grid">
|
||||
<view v-for="(item, idx) in editCache" :key="idx" class="edit-item"
|
||||
:class="{ active: item.selected }" @click="toggleSelect(item)">
|
||||
<view class="icon-box">
|
||||
<u-icon :name="item.icon" :size="48" :custom-prefix='item.prefix'
|
||||
:color="item.selected ? pastelColors[idx % 6] : '#ccc'" />
|
||||
</view>
|
||||
<text class="edit-label">{{ item.name }}</text>
|
||||
<view v-if="item.selected" class="selected-mark">
|
||||
<u-icon name="checkmark" :size="24" color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="edit-btns">
|
||||
<u-button size="medium" class="btn-cancel" @click="showEditQuick = false">取消</u-button>
|
||||
<u-button size="medium" class="btn-confirm" type="primary" @click="handleConfirmEdit">确定</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
<nbd-select-project v-model="showProject" :select-id="currentProject?.ProjectId" :list="projectList"
|
||||
label-key="ProjectName" value-key="ProjectId" @confirm="handleConfirmProject" />
|
||||
label-key="ProjectName" value-key="ProjectId" :search-key="projectNameKeyword"
|
||||
@search-change="handleSearchChange" @confirm="handleConfirmProject" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -73,13 +137,18 @@
|
||||
ref
|
||||
} from 'vue'
|
||||
import {
|
||||
useUserStore
|
||||
useUserStore,
|
||||
useMenuStore,
|
||||
useLargeStore
|
||||
} from '@/store';
|
||||
|
||||
import {
|
||||
storeToRefs
|
||||
} from 'pinia';
|
||||
|
||||
const userStore = useUserStore()
|
||||
const menuStore = useMenuStore()
|
||||
const largeStore = useLargeStore()
|
||||
const {
|
||||
setProjectList,
|
||||
setCurrentProject
|
||||
@@ -89,12 +158,52 @@
|
||||
projectList,
|
||||
currentProject
|
||||
} = storeToRefs(userStore)
|
||||
const {
|
||||
quickApps,
|
||||
queryApps,
|
||||
editCache
|
||||
} = storeToRefs(menuStore)
|
||||
const {
|
||||
hjIndexData
|
||||
} = storeToRefs(largeStore)
|
||||
const {
|
||||
pastelColors
|
||||
} = menuStore
|
||||
const navbarBg = reactive({
|
||||
backgroundColor: "var(--u-type-primary)"
|
||||
})
|
||||
const projectNameKeyword = ref('')
|
||||
const showProject = ref(false)
|
||||
const showEditQuick = ref(false)
|
||||
|
||||
const currentProjectName = computed(()=>currentProject.value?.ProjectName||'')
|
||||
const currentProjectName = computed(() => currentProject.value?.ProjectName || '')
|
||||
|
||||
const handleSkip= (item) => {
|
||||
uni.navigateTo({
|
||||
url: item.path
|
||||
})
|
||||
}
|
||||
|
||||
// 打开编辑弹窗
|
||||
const openEditQuick = () => {
|
||||
menuStore.openEdit()
|
||||
showEditQuick.value = true
|
||||
}
|
||||
|
||||
// 切换选择状态
|
||||
const toggleSelect = (item) => {
|
||||
menuStore.toggleSelect(item)
|
||||
}
|
||||
|
||||
// 确认编辑
|
||||
const handleConfirmEdit = () => {
|
||||
menuStore.confirmEdit()
|
||||
showEditQuick.value = false
|
||||
uni.showToast({
|
||||
title: '已更新快捷应用',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
const handleShowProjectSelect = () => {
|
||||
reqProjectByUserId(userInfo.value.PersonId).then(res => {
|
||||
@@ -105,23 +214,29 @@
|
||||
/**
|
||||
* 项目列表点击确认
|
||||
*/
|
||||
const handleConfirmProject = (item)=>{
|
||||
const handleConfirmProject = (item) => {
|
||||
setCurrentProject(item)
|
||||
showProject.value = false
|
||||
}
|
||||
/**
|
||||
* 项目按名称搜索
|
||||
*/
|
||||
const handleSearchChange = (keyword) => {
|
||||
projectNameKeyword.value = keyword
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫一扫
|
||||
*/
|
||||
const handleScan = ()=>{
|
||||
const handleScan = () => {
|
||||
uni.scanCode({
|
||||
onlyFromCamera: true,
|
||||
success (res) {
|
||||
success(res) {
|
||||
console.log('条码类型:' + res.scanType);
|
||||
console.log('条码内容:' + res.result);
|
||||
},
|
||||
fail(err) {
|
||||
console.log("扫码失败了====>",err)
|
||||
console.log("扫码失败了====>", err)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<view class="mine">
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-card">
|
||||
<view class="user-info">
|
||||
<u-avatar :size="120" icon="account" bgColor="#e0e7ff"></u-avatar>
|
||||
<view class="user-detail">
|
||||
<text class="user-name">{{ userInfo?.PersonName || '未登录' }}</text>
|
||||
<text class="user-account">账号:{{ userInfo?.Account || '-' }}</text>
|
||||
<text class="user-company">{{ userInfo?.CompanyName || '暂无公司信息' }}</text>
|
||||
<text class="user-phone">{{ userInfo?.Telephone || '暂无手机号' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<u-button v-if="!token" size="large" shape="circle" @click="goLogin">
|
||||
立即登录
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 项目信息 -->
|
||||
<view class="project-card" v-if="currentProject?.ProjectId">
|
||||
<view class="card-title">
|
||||
<u-icon name="home" :size="36" color="#2979ff"></u-icon>
|
||||
<text>当前项目</text>
|
||||
</view>
|
||||
<view class="project-info">
|
||||
<text class="project-name">{{ currentProject.ProjectName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" @click="handleMenuClick('profile')">
|
||||
<view class="menu-left">
|
||||
<u-icon name="setting" :size="40" color="#2979ff"></u-icon>
|
||||
<text>个人设置</text>
|
||||
</view>
|
||||
<u-icon name="arrow-right" :size="28" color="#ccc"></u-icon>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="handleMenuClick('password')">
|
||||
<view class="menu-left">
|
||||
<u-icon name="lock" :size="40" color="#2979ff"></u-icon>
|
||||
<text>修改密码</text>
|
||||
</view>
|
||||
<u-icon name="arrow-right" :size="28" color="#ccc"></u-icon>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @click="handleMenuClick('about')">
|
||||
<view class="menu-left">
|
||||
<u-icon name="info-circle" :size="40" color="#2979ff"></u-icon>
|
||||
<text>关于我们</text>
|
||||
</view>
|
||||
<u-icon name="arrow-right" :size="28" color="#ccc"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录 -->
|
||||
<view class="logout-btn" v-if="token">
|
||||
<u-button text="退出登录" @click="handleLogout"></u-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
computed
|
||||
} from 'vue'
|
||||
import {
|
||||
useUserStore
|
||||
} from '@/store'
|
||||
import {
|
||||
storeToRefs
|
||||
} from 'pinia'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const {
|
||||
logout
|
||||
} = userStore
|
||||
const {
|
||||
userInfo,
|
||||
token,
|
||||
currentProject
|
||||
} = storeToRefs(userStore)
|
||||
|
||||
// 跳转登录
|
||||
const goLogin = () => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
})
|
||||
}
|
||||
|
||||
// 切换项目
|
||||
const handleSwitchProject = () => {
|
||||
// TODO: 打开项目选择弹窗
|
||||
uni.showToast({
|
||||
title: '项目选择功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 菜单点击
|
||||
const handleMenuClick = (type) => {
|
||||
const menuMap = {
|
||||
profile: '个人设置',
|
||||
password: '修改密码',
|
||||
about: '关于我们'
|
||||
}
|
||||
uni.showToast({
|
||||
title: `${menuMap[type]}功能开发中`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const handleLogout = () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
logout()
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mine {
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
// 用户信息卡片
|
||||
.user-card {
|
||||
background: linear-gradient(135deg, #2979ff 0%, #6ba7ff 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
color: #ffffff;
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
.user-detail {
|
||||
margin-left: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
.user-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.user-account,
|
||||
.user-company,
|
||||
.user-phone {
|
||||
font-size: 24rpx;
|
||||
opacity: 0.85;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.user-account {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.user-company {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.user-phone {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.u-button {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
color: #ffffff;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 项目信息卡片
|
||||
.project-card {
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.u-icon {
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.project-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.project-name {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 功能菜单
|
||||
.menu-list {
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 0 32rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.u-icon {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
.logout-btn {
|
||||
margin-top: 48rpx;
|
||||
|
||||
.u-button {
|
||||
height: 88rpx;
|
||||
font-size: 30rpx;
|
||||
border-radius: 44rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 头部过滤区域 -->
|
||||
<view class="filter-header">
|
||||
<view class="filter-row">
|
||||
<!-- 下拉过滤 1 -->
|
||||
<u-dropdown v-model="filter1Value" :options="filter1Options" @change="handleFilter1Change"></u-dropdown>
|
||||
|
||||
<!-- 下拉过滤 2 -->
|
||||
<u-dropdown v-model="filter2Value" :options="filter2Options" @change="handleFilter2Change"></u-dropdown>
|
||||
|
||||
<!-- 刷新按钮 -->
|
||||
<view class="refresh-btn" @click="handleRefresh">
|
||||
<u-icon name="reload" :size="36" :class="{ rotating: loading }"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<scroll-view
|
||||
class="list-scroll"
|
||||
scroll-y
|
||||
:scroll-into-view="scrollIntoView"
|
||||
@scrolltolower="loadMore"
|
||||
refresher-enabled
|
||||
:refresher-triggered="refreshing"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<!-- 列表项 -->
|
||||
<view class="list-wrap">
|
||||
<view
|
||||
v-for="(item, index) in listData"
|
||||
:key="item.id || index"
|
||||
class="list-item"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<view class="item-header">
|
||||
<text class="item-title">{{ item.name || '未命名' }}</text>
|
||||
<text class="item-status" :class="item.status">{{ item.statusText || '' }}</text>
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<text class="item-desc">{{ item.description || '' }}</text>
|
||||
</view>
|
||||
<view class="item-footer">
|
||||
<text class="item-time">{{ item.createTime || '' }}</text>
|
||||
<text class="item-author">{{ item.creator || '' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<u-empty
|
||||
v-if="!loading && listData.length === 0"
|
||||
mode="list"
|
||||
text="暂无数据"
|
||||
></u-empty>
|
||||
|
||||
<!-- 加载提示 -->
|
||||
<view v-if="loading && listData.length > 0" class="loading-more">
|
||||
<u-loading mode="circle" size="32"></u-loading>
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多 -->
|
||||
<view v-if="!loading && !hasMore && listData.length > 0" class="no-more">
|
||||
<text>— 没有更多了 —</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
|
||||
// ===== 过滤条件 =====
|
||||
const filter1Value = ref('')
|
||||
const filter2Value = ref('')
|
||||
|
||||
// 过滤选项 1
|
||||
const filter1Options = ref([
|
||||
{ label: '全部类型', value: '' },
|
||||
{ label: '类型 A', value: 'type_a' },
|
||||
{ label: '类型 B', value: 'type_b' },
|
||||
{ label: '类型 C', value: 'type_c' }
|
||||
])
|
||||
|
||||
// 过滤选项 2
|
||||
const filter2Options = ref([
|
||||
{ label: '全部状态', value: '' },
|
||||
{ label: '进行中', value: 'pending' },
|
||||
{ label: '已完成', value: 'completed' },
|
||||
{ label: '已取消', value: 'cancelled' }
|
||||
])
|
||||
|
||||
// ===== 列表数据 =====
|
||||
const listData = ref([])
|
||||
const loading = ref(false)
|
||||
const refreshing = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = 20
|
||||
const scrollIntoView = ref('')
|
||||
|
||||
// ===== 筛选条件变更 =====
|
||||
const filterParams = reactive({
|
||||
filter1: '',
|
||||
filter2: ''
|
||||
})
|
||||
|
||||
// 过滤 1 变化
|
||||
const handleFilter1Change = (value) => {
|
||||
filterParams.filter1 = value
|
||||
resetAndLoad()
|
||||
}
|
||||
|
||||
// 过滤 2 变化
|
||||
const handleFilter2Change = (value) => {
|
||||
filterParams.filter2 = value
|
||||
resetAndLoad()
|
||||
}
|
||||
|
||||
// ===== 数据加载 =====
|
||||
|
||||
// 加载数据
|
||||
const fetchData = async (isRefresh = false) => {
|
||||
if (loading.value || (!isRefresh && !hasMore.value)) return
|
||||
|
||||
loading.value = true
|
||||
if (isRefresh) {
|
||||
refreshing.value = true
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: 替换为实际 API 调用
|
||||
// const res = await api.getList({
|
||||
// page: currentPage.value,
|
||||
// pageSize,
|
||||
// ...filterParams
|
||||
// })
|
||||
|
||||
// 模拟数据
|
||||
const mockData = await mockApiFetch({
|
||||
page: currentPage.value,
|
||||
pageSize,
|
||||
...filterParams
|
||||
})
|
||||
|
||||
if (isRefresh) {
|
||||
listData.value = mockData.list
|
||||
} else {
|
||||
listData.value = [...listData.value, ...mockData.list]
|
||||
}
|
||||
|
||||
hasMore.value = mockData.hasMore
|
||||
currentPage.value++
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error)
|
||||
uni.showToast({
|
||||
title: '加载失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置并重新加载
|
||||
const resetAndLoad = () => {
|
||||
currentPage.value = 1
|
||||
hasMore.value = true
|
||||
listData.value = []
|
||||
fetchData(true)
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const handleRefresh = () => {
|
||||
resetAndLoad()
|
||||
}
|
||||
|
||||
// 上拉加载更多
|
||||
const loadMore = () => {
|
||||
if (!loading.value && hasMore.value) {
|
||||
fetchData(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 列表项点击
|
||||
const handleItemClick = (item) => {
|
||||
console.log('点击列表项:', item)
|
||||
// TODO: 跳转详情
|
||||
// uni.navigateTo({ url: `/pages/xxx/detail?id=${item.id}` })
|
||||
}
|
||||
|
||||
// ===== 模拟 API(实际使用时删除)=====
|
||||
const mockApiFetch = async (params) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const start = (params.page - 1) * params.pageSize
|
||||
const end = start + params.pageSize
|
||||
const hasMore = end < 100 // 模拟总共 100 条数据
|
||||
|
||||
const list = Array.from({ length: hasMore ? params.pageSize : (100 - start) }, (_, i) => ({
|
||||
id: start + i + 1,
|
||||
name: `项目 ${start + i + 1}`,
|
||||
description: '这是一个测试项目的描述信息,可能包含更多内容',
|
||||
status: ['pending', 'completed', 'cancelled'][Math.floor(Math.random() * 3)],
|
||||
statusText: ['进行中', '已完成', '已取消'][Math.floor(Math.random() * 3)],
|
||||
createTime: '2024-01-01 12:00:00',
|
||||
creator: `用户${Math.floor(Math.random() * 10) + 1}`
|
||||
}))
|
||||
|
||||
resolve({
|
||||
list,
|
||||
hasMore
|
||||
})
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
// ===== 生命周期 =====
|
||||
onMounted(() => {
|
||||
fetchData(true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
// 头部过滤区域
|
||||
.filter-header {
|
||||
background: #ffffff;
|
||||
padding: 20rpx 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
margin-left: auto;
|
||||
padding: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.rotating {
|
||||
animation: rotate 1s linear infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
// 列表滚动区域
|
||||
.list-scroll {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
// 列表项
|
||||
.list-item {
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.item-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-status {
|
||||
font-size: 22rpx;
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #f0f0f0;
|
||||
color: #666;
|
||||
|
||||
&.pending {
|
||||
background: rgba(255, 167, 38, 0.1);
|
||||
color: #ffa726;
|
||||
}
|
||||
|
||||
&.completed {
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
&.cancelled {
|
||||
background: rgba(244, 67, 54, 0.1);
|
||||
color: #f44336;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-content {
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.item-desc {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
|
||||
.item-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.item-time,
|
||||
.item-author {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载中
|
||||
.loading-more {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 32rpx 0;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
// 没有更多
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 32rpx 0;
|
||||
color: #ccc;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -8,4 +8,6 @@ export function setupStore(app) {
|
||||
}
|
||||
|
||||
export * from './modules/user'
|
||||
export * from './modules/menu'
|
||||
export * from './modules/large'
|
||||
export default pinia
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
defineStore
|
||||
} from 'pinia'
|
||||
import {
|
||||
ref,
|
||||
computed
|
||||
} from 'vue'
|
||||
import {reqHJIndexData} from '@/api/hj.js'
|
||||
|
||||
export const useLargeStore = defineStore('large', () => {
|
||||
const hjIndexData = ref(null)
|
||||
|
||||
/**
|
||||
* 获取焊接首页数据
|
||||
*/
|
||||
const fetchHJIndex = (projectId)=>{
|
||||
reqHJIndexData(projectId).then(res=>{
|
||||
hjIndexData.value = res.data
|
||||
})
|
||||
}
|
||||
return {
|
||||
hjIndexData,
|
||||
fetchHJIndex
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,102 @@
|
||||
import {
|
||||
defineStore
|
||||
} from 'pinia'
|
||||
import {
|
||||
ref,
|
||||
computed
|
||||
} from 'vue'
|
||||
import {pastelColors,defaultApps} from '@/utils/constant.js'
|
||||
import {reqMenuPowerList} from '@/api/auth.js'
|
||||
|
||||
export const useMenuStore = defineStore('menu', () => {
|
||||
// 应用列表(默认配置 + 持久化)
|
||||
const appsList = ref([...defaultApps])
|
||||
// 编辑时的临时缓存
|
||||
const editCache = ref([])
|
||||
// 菜单的权限列表(后端返回的有权限的菜单 code 数组)
|
||||
const menuProwerList = ref([])
|
||||
|
||||
// 快捷应用列表(已选择的)
|
||||
const quickApps = computed(() => appsList.value.filter(app => app.selected&&hasMenuPermission(app.menuId)))
|
||||
|
||||
// 业务查询列表(未选择的)
|
||||
const queryApps = computed(() => appsList.value.filter(app => !app.selected&&hasMenuPermission(app.menuId)))
|
||||
|
||||
/**
|
||||
* 打开编辑弹窗(备份当前状态)
|
||||
*/
|
||||
const openEdit = () => {
|
||||
editCache.value = appsList.value.filter(app => hasMenuPermission(app.menuId))
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换应用选择状态
|
||||
*/
|
||||
const toggleSelect = (item) => {
|
||||
item.selected = !item.selected
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认编辑(保存更改)
|
||||
*/
|
||||
const confirmEdit = () => {
|
||||
appsList.value = editCache.value.map(app => ({ ...app }))
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求菜单权限列表
|
||||
*/
|
||||
const fetchMenuPowerList = async (userId, projectId) => {
|
||||
if (!userId || !projectId) return
|
||||
try {
|
||||
const res = await reqMenuPowerList(userId, projectId)
|
||||
menuProwerList.value = res.data || []
|
||||
} catch (err) {
|
||||
console.error('获取菜单权限失败:', err)
|
||||
menuProwerList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断菜单是否有权限打开
|
||||
* @param {string} menuCode - 菜单编码/标识
|
||||
* @returns {boolean} 是否有权限
|
||||
*/
|
||||
const hasMenuPermission = (menuCode) => {
|
||||
if (!menuCode) return true
|
||||
return menuProwerList.value.includes(menuCode)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据权限过滤快捷应用
|
||||
*/
|
||||
const quickAppsWithPermission = computed(() => {
|
||||
if (menuProwerList.value.length === 0) return quickApps.value
|
||||
return quickApps.value.filter(app => {
|
||||
return hasMenuPermission(app.permissionCode)
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
appsList,
|
||||
editCache,
|
||||
quickApps,
|
||||
quickAppsWithPermission,
|
||||
queryApps,
|
||||
menuProwerList,
|
||||
pastelColors,
|
||||
openEdit,
|
||||
toggleSelect,
|
||||
confirmEdit,
|
||||
fetchMenuPowerList,
|
||||
hasMenuPermission
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [{
|
||||
key: 'menu',
|
||||
paths: [],
|
||||
}]
|
||||
},
|
||||
})
|
||||
@@ -2,12 +2,13 @@ import {
|
||||
defineStore
|
||||
} from 'pinia'
|
||||
import {
|
||||
ref
|
||||
ref,
|
||||
watch
|
||||
} from 'vue'
|
||||
import {
|
||||
reqLogin
|
||||
} from '@/api/auth.js'
|
||||
|
||||
import { useMenuStore, useLargeStore } from '@/store'
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const token = ref("")
|
||||
const userInfo = ref(null)
|
||||
@@ -20,6 +21,20 @@ export const useUserStore = defineStore('user', () => {
|
||||
const projectList = ref([])
|
||||
const currentProject = ref(null)
|
||||
|
||||
// 监听 currentProject 变化,获取菜单权限
|
||||
watch(() => currentProject.value?.ProjectId, (newId) => {
|
||||
if (newId) {
|
||||
// 项目变化 请求菜单权限
|
||||
const menuStore = useMenuStore()
|
||||
const userId = userInfo.value?.PersonId
|
||||
menuStore.fetchMenuPowerList(userId, newId)
|
||||
// 项目变化 请求焊接首页数据
|
||||
const largeStore = useLargeStore()
|
||||
largeStore.fetchHJIndex(newId)
|
||||
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
|
||||
/**
|
||||
* 登出
|
||||
@@ -27,11 +42,11 @@ export const useUserStore = defineStore('user', () => {
|
||||
const logout = () => {
|
||||
token.value = ""
|
||||
userInfo.value = null
|
||||
loginForm.value = {
|
||||
account: '',
|
||||
password: '',
|
||||
Telephone: '',
|
||||
}
|
||||
// loginForm.value = {
|
||||
// account: '',
|
||||
// password: '',
|
||||
// Telephone: '',
|
||||
// }
|
||||
currentProject.value = null
|
||||
}
|
||||
|
||||
@@ -79,7 +94,8 @@ export const useUserStore = defineStore('user', () => {
|
||||
projectList,
|
||||
currentProject,
|
||||
setProjectList,
|
||||
setCurrentProject
|
||||
setCurrentProject,
|
||||
logout
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// 淡雅配色方案(马卡龙色系)
|
||||
export const pastelColors = [
|
||||
'#7eb6ff', // 淡蓝
|
||||
'#ffb787', // 淡橙
|
||||
'#8fd6a8', // 淡绿
|
||||
'#b99bf8', // 淡紫
|
||||
'#ff8fa3', // 淡粉
|
||||
'#80deea', // 青色
|
||||
'#ffd56b', // 淡黄
|
||||
]
|
||||
|
||||
// 默认应用配置
|
||||
export const defaultApps = [{
|
||||
name: '点口管理',
|
||||
icon: 'diankouguanli',
|
||||
menuId: '3ACE25CE-C5CE-4CEC-AD27-0D5CF1DF2F01',
|
||||
path: "/pipe/diankou/list",
|
||||
prefix: 'nbd-icon',
|
||||
selected: true
|
||||
},
|
||||
{
|
||||
name: '试压管理',
|
||||
icon: 'shiyabao',
|
||||
menuId: '55976B16-2C33-406E-B514-2FE42D031071',
|
||||
prefix: 'nbd-icon',
|
||||
selected: true
|
||||
},
|
||||
{
|
||||
name: '包装管理',
|
||||
icon: 'baozhuangguanli',
|
||||
menuId: "25DED954-10C9-47CC-99F2-C44FDE9E0A81",
|
||||
prefix: 'nbd-icon',
|
||||
selected: true
|
||||
},
|
||||
{
|
||||
name: '发货管理',
|
||||
icon: 'a-facheguanli',
|
||||
menuId: "EEC0D060-C15E-4D25-B015-C2B91F735DAC",
|
||||
prefix: 'nbd-icon',
|
||||
selected: true
|
||||
},
|
||||
{
|
||||
name: '预制组件管理',
|
||||
icon: 'yuzhijian',
|
||||
menuId: "8255554C-0A92-4C7B-BF19-779AF0220A8C",
|
||||
prefix: 'nbd-icon',
|
||||
selected: true
|
||||
}
|
||||
]
|
||||