修复 admin 构建门禁与类型错误,清理死代码
- tsconfig.node.json 启用 composite,让 vue-tsc --noEmit 真正生效 - 修复 24 处 antd-vue 4.x 类型错误(Table columns、Tree data、 RangePicker null、Progress format 回调等) - 删除未被引用的 axios 封装与 api 聚合(admin/utils/request.ts、 admin/api/index.ts、miniprogram/utils/request.js) - 新增 .githooks/pre-commit:admin 源码变更时自动 pnpm run build Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
f9772b99cb
commit
077495a401
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
# 仅当本次提交涉及 admin 源代码或配置时,运行 admin 构建(含 vue-tsc 类型检查)
|
||||
# 通过检查暂存区是否有 zhican/admin/ 下的相关文件来决定
|
||||
|
||||
set -e
|
||||
|
||||
CHANGED=$(git diff --cached --name-only --diff-filter=ACMR \
|
||||
| grep -E '^zhican/admin/(src/|.*\.(json|ts|html))' || true)
|
||||
|
||||
if [ -z "$CHANGED" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "→ pre-commit: 检测到 admin 变更,运行 pnpm run build..."
|
||||
cd "$(git rev-parse --show-toplevel)/zhican/admin"
|
||||
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "pre-commit: 未找到 pnpm,跳过构建检查(请手动运行 pnpm run build 验证)" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pnpm run build
|
||||
@@ -1,39 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 绿色种采 - 土地环境
|
||||
export const landEnvApi = {
|
||||
list: (params?: any) => request.get('/green-planting/land-env', { params }),
|
||||
detail: (id: string) => request.get(`/green-planting/land-env/${id}`),
|
||||
create: (data: any) => request.post('/green-planting/land-env', data),
|
||||
update: (id: string, data: any) => request.put(`/green-planting/land-env/${id}`, data),
|
||||
remove: (id: string) => request.delete(`/green-planting/land-env/${id}`),
|
||||
}
|
||||
|
||||
// 卫生供应 - 卫生检测
|
||||
export const hygieneTestApi = {
|
||||
list: (params?: any) => request.get('/hygiene-supply/hygiene-test', { params }),
|
||||
detail: (id: string) => request.get(`/hygiene-supply/hygiene-test/${id}`),
|
||||
create: (data: any) => request.post('/hygiene-supply/hygiene-test', data),
|
||||
}
|
||||
|
||||
// 健康生产 - 食安监控
|
||||
export const foodSafetyApi = {
|
||||
list: (params?: any) => request.get('/health-production/food-safety', { params }),
|
||||
detail: (id: string) => request.get(`/health-production/food-safety/${id}`),
|
||||
}
|
||||
|
||||
// 营养用餐 - 用户管理
|
||||
export const userMgmtApi = {
|
||||
list: (params?: any) => request.get('/nutrition-dining/users', { params }),
|
||||
detail: (id: string) => request.get(`/nutrition-dining/users/${id}`),
|
||||
create: (data: any) => request.post('/nutrition-dining/users', data),
|
||||
update: (id: string, data: any) => request.put(`/nutrition-dining/users/${id}`, data),
|
||||
remove: (id: string) => request.delete(`/nutrition-dining/users/${id}`),
|
||||
}
|
||||
|
||||
// 登录
|
||||
export const authApi = {
|
||||
login: (data: { username: string; password: string }) => request.post('/auth/login', data),
|
||||
logout: () => request.post('/auth/logout'),
|
||||
profile: () => request.get('/auth/profile'),
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
// ==================== 通用工具 ====================
|
||||
export const randomId = () => Math.random().toString(36).slice(2, 10)
|
||||
export const randomNum = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min
|
||||
@@ -40,7 +38,7 @@ const supplierNames = ['绿源农业', '丰禾食品', '天然牧场', '鲜美
|
||||
const materialNames = ['有机大米', '土鸡蛋', '新鲜猪肉', '活鱼', '时令蔬菜', '有机牛奶', '散养鸡', '绿色面粉', '菜籽油', '食用盐']
|
||||
|
||||
export function genLandEnvData(n = 12) {
|
||||
return Array.from({ length: n }, (_, i) => ({
|
||||
return Array.from({ length: n }, () => ({
|
||||
id: randomId(),
|
||||
name: pick(landNames),
|
||||
area: randomFloat(5, 200, 0) + '亩',
|
||||
@@ -372,8 +370,6 @@ export function genProductionStatsData() {
|
||||
}
|
||||
|
||||
// ==================== 营养用餐 ====================
|
||||
const nutrientNames = ['蛋白质', '脂肪', '碳水化合物', '膳食纤维', '维生素A', '维生素C', '钙', '铁', '锌']
|
||||
|
||||
export function genNutritionTestData(n = 12) {
|
||||
return Array.from({ length: n }, () => ({
|
||||
id: randomId(),
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import axios from 'axios'
|
||||
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
const request: AxiosInstance = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 15000,
|
||||
})
|
||||
|
||||
request.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
)
|
||||
|
||||
request.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
const { code, msg, data } = response.data
|
||||
if (code === 0) {
|
||||
return data
|
||||
}
|
||||
message.error(msg || '请求失败')
|
||||
return Promise.reject(new Error(msg))
|
||||
},
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('token')
|
||||
window.location.href = '/login'
|
||||
}
|
||||
message.error(error.message || '网络错误')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default request
|
||||
@@ -245,11 +245,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import ChartPanel from '@/components/common/ChartPanel.vue'
|
||||
import { genDashboardData } from '@/mock/data'
|
||||
|
||||
const router = useRouter()
|
||||
const d = reactive(genDashboardData())
|
||||
const filterCanteen = ref('')
|
||||
const trendMetric = ref<'orders' | 'revenue'>('orders')
|
||||
|
||||
@@ -111,7 +111,7 @@ const relatedLinks = [
|
||||
|
||||
const data = ref(genHarvestData(15))
|
||||
const searchText = ref('')
|
||||
const dateRange = ref<[Dayjs, Dayjs] | null>(null)
|
||||
const dateRange = ref<[Dayjs, Dayjs]>()
|
||||
const statusFilter = ref<string | undefined>(undefined)
|
||||
const modalVisible = ref(false)
|
||||
const currentRecord = ref<any>(null)
|
||||
|
||||
@@ -114,7 +114,7 @@ const relatedLinks = [
|
||||
]
|
||||
|
||||
const data = ref(genProcurementData(12))
|
||||
const dateRange = ref<[Dayjs, Dayjs] | null>(null)
|
||||
const dateRange = ref<[Dayjs, Dayjs]>()
|
||||
const statusFilter = ref<string | undefined>(undefined)
|
||||
const modalVisible = ref(false)
|
||||
|
||||
@@ -127,7 +127,7 @@ const form = ref({
|
||||
quantity: 100,
|
||||
unit: 'kg',
|
||||
unitPrice: 0,
|
||||
deliveryDate: null as Dayjs | null,
|
||||
deliveryDate: undefined as Dayjs | undefined,
|
||||
remark: '',
|
||||
})
|
||||
|
||||
@@ -173,7 +173,7 @@ function showModal() {
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.value = { material: undefined, supplier: undefined, quantity: 100, unit: 'kg', unitPrice: 0, deliveryDate: null, remark: '' }
|
||||
form.value = { material: undefined, supplier: undefined, quantity: 100, unit: 'kg', unitPrice: 0, deliveryDate: undefined, remark: '' }
|
||||
}
|
||||
|
||||
function handleApprove(record: any) {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
v-if="record.status !== 'done'"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="confirmAssemble(record)"
|
||||
@click="confirmAssemble(record as AssembleItem)"
|
||||
>确认组配</a-button>
|
||||
<span v-else style="color: #999">已完成</span>
|
||||
</template>
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { statusMap, randomId, randomNum, randomFloat, pick, pickMulti, randomTime } from '@/mock/data'
|
||||
import { statusMap, randomId, randomNum, randomFloat, pickMulti, randomTime } from '@/mock/data'
|
||||
|
||||
const vegs = ['白菜', '萝卜', '番茄', '黄瓜', '茄子', '青椒', '芹菜', '菠菜']
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { genCookByOrderData, statusMap, canteenNames } from '@/mock/data'
|
||||
import { genCookByOrderData, canteenNames } from '@/mock/data'
|
||||
import StatCard from '@/components/common/StatCard.vue'
|
||||
import RelatedModules from '@/components/common/RelatedModules.vue'
|
||||
|
||||
|
||||
@@ -74,12 +74,12 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import { genSurplusData, statusMap } from '@/mock/data'
|
||||
import { genSurplusData } from '@/mock/data'
|
||||
import StatCard from '@/components/common/StatCard.vue'
|
||||
import ChartPanel from '@/components/common/ChartPanel.vue'
|
||||
|
||||
const data = ref(genSurplusData(8))
|
||||
const dateRange = ref<[Dayjs, Dayjs] | null>(null)
|
||||
const dateRange = ref<[Dayjs, Dayjs]>()
|
||||
const disposalFilter = ref<string | undefined>(undefined)
|
||||
const modalVisible = ref(false)
|
||||
const currentRecord = ref<any>(null)
|
||||
|
||||
@@ -93,7 +93,7 @@ import { genHygieneTestData, statusMap } from '@/mock/data'
|
||||
const tableData = ref(genHygieneTestData(30))
|
||||
const searchText = ref('')
|
||||
const resultFilter = ref('')
|
||||
const dateRange = ref<[Dayjs, Dayjs] | null>(null)
|
||||
const dateRange = ref<[Dayjs, Dayjs]>()
|
||||
const drawerVisible = ref(false)
|
||||
const currentRecord = ref<any>(null)
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
<a-descriptions :column="2" bordered size="small">
|
||||
<a-descriptions-item label="姓名">{{ personProfile.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="部门">{{ personProfile.dept }}</a-descriptions-item>
|
||||
<a-descriptions-item label="日均热量">{{ personProfile.avgCalories }} kcal</a-descriptions-item>
|
||||
<a-descriptions-item label="日均热量">{{ personProfile.calories }} kcal</a-descriptions-item>
|
||||
<a-descriptions-item label="营养评分">
|
||||
<a-progress :percent="personProfile.score" :size="20"
|
||||
:stroke-color="personProfile.score >= 80 ? '#52c41a' : personProfile.score >= 60 ? '#faad14' : '#f5222d'" />
|
||||
@@ -179,7 +179,7 @@
|
||||
:percent="record.score"
|
||||
:size="20"
|
||||
:stroke-color="record.score >= 80 ? '#52c41a' : record.score >= 60 ? '#faad14' : '#f5222d'"
|
||||
:format="(p: number) => p + ''"
|
||||
:format="(p?: number) => (p ?? 0) + ''"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'verdict'">
|
||||
@@ -251,7 +251,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import ChartPanel from '@/components/common/ChartPanel.vue'
|
||||
import { randomNum, randomFloat, pick } from '@/mock/data'
|
||||
|
||||
@@ -267,19 +267,6 @@ const drawerPerson = ref<any>(null)
|
||||
const personList = ['张伟', '李芳', '王娜', '刘敏', '陈强', '杨磊', '黄艳', '赵勇', '周洋', '吴军']
|
||||
const deptList = ['研发部', '市场部', '财务部', '行政部']
|
||||
|
||||
// ============ 营养标准 ============
|
||||
const standards = {
|
||||
calories: { min: 1800, max: 2200, recommend: 2000 },
|
||||
protein: { min: 55, max: 85, recommend: 70, unit: 'g' },
|
||||
fat: { min: 45, max: 75, recommend: 60, unit: 'g' },
|
||||
carbs: { min: 250, max: 340, recommend: 300, unit: 'g' },
|
||||
fiber: { min: 20, max: 35, recommend: 25, unit: 'g' },
|
||||
calcium: { min: 700, max: 1000, recommend: 800, unit: 'mg' },
|
||||
iron: { min: 10, max: 20, recommend: 15, unit: 'mg' },
|
||||
vitC: { min: 80, max: 150, recommend: 100, unit: 'mg' },
|
||||
sodium: { min: 1000, max: 2300, recommend: 1500, unit: 'mg' },
|
||||
}
|
||||
|
||||
// ============ Mock 就餐数据 ============
|
||||
function genPersonNutrition() {
|
||||
return personList.map(name => {
|
||||
@@ -457,7 +444,7 @@ const mealBalance = computed(() => {
|
||||
// ============ 个人营养画像 ============
|
||||
const personProfile = computed(() => {
|
||||
const p = allPersonData.value.find(d => d.name === filterPerson.value)
|
||||
return p || { name: '', dept: '', avgCalories: 0, score: 0, consecutiveDays: 0, bmi: 0 }
|
||||
return p || { name: '', dept: '', calories: 0, score: 0, consecutiveDays: 0, bmi: 0 }
|
||||
})
|
||||
|
||||
const personNutrientData = computed(() => {
|
||||
|
||||
@@ -150,10 +150,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import type { ColumnsType } from 'ant-design-vue/es/table'
|
||||
import StatCard from '@/components/common/StatCard.vue'
|
||||
import { canteenList } from '@/mock/data'
|
||||
|
||||
const columns = [
|
||||
const columns: ColumnsType<any> = [
|
||||
{ title: '食堂名称', dataIndex: 'name', key: 'name', width: 110 },
|
||||
{ title: '别名', dataIndex: 'alias', key: 'alias', width: 120 },
|
||||
{ title: '地址', dataIndex: 'address', key: 'address', width: 100 },
|
||||
|
||||
@@ -145,10 +145,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import dayjs, { type Dayjs } from 'dayjs'
|
||||
import type { ColumnsType } from 'ant-design-vue/es/table'
|
||||
import StatCard from '@/components/common/StatCard.vue'
|
||||
|
||||
const columns = [
|
||||
const columns: ColumnsType<any> = [
|
||||
{ title: '姓名', dataIndex: 'name', key: 'name', width: 100 },
|
||||
{ title: '性别', dataIndex: 'gender', key: 'gender', width: 60 },
|
||||
{ title: '手机号', dataIndex: 'phone', key: 'phone', width: 120 },
|
||||
@@ -183,13 +184,20 @@ const drawerVisible = ref(false)
|
||||
const editingId = ref('')
|
||||
const viewingEmployee = ref<any>(null)
|
||||
|
||||
const form = reactive({
|
||||
const form = reactive<{
|
||||
name: string
|
||||
gender: string
|
||||
phone: string
|
||||
department: string
|
||||
position: string
|
||||
entryDate: Dayjs | undefined
|
||||
}>({
|
||||
name: '',
|
||||
gender: '男',
|
||||
phone: '',
|
||||
department: '',
|
||||
position: '',
|
||||
entryDate: null,
|
||||
entryDate: undefined,
|
||||
})
|
||||
|
||||
const activeEmployees = computed(() => allEmployees.value.filter(e => e.status === '在职'))
|
||||
@@ -227,7 +235,7 @@ function showAddModal() {
|
||||
form.phone = ''
|
||||
form.department = ''
|
||||
form.position = ''
|
||||
form.entryDate = null
|
||||
form.entryDate = undefined
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
@@ -270,7 +278,7 @@ function saveEmployee() {
|
||||
phone: form.phone,
|
||||
department: form.department,
|
||||
position: form.position,
|
||||
entryDate: form.entryDate?.format('YYYY-MM-DD'),
|
||||
entryDate: form.entryDate?.format('YYYY-MM-DD') ?? '',
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -281,7 +289,7 @@ function saveEmployee() {
|
||||
phone: form.phone,
|
||||
department: form.department,
|
||||
position: form.position,
|
||||
entryDate: form.entryDate?.format('YYYY-MM-DD'),
|
||||
entryDate: form.entryDate?.format('YYYY-MM-DD') ?? '',
|
||||
status: '试用期',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
/>
|
||||
<a-tree
|
||||
v-model:selectedKeys="selectedOrgKeys"
|
||||
:tree-data="orgTreeData"
|
||||
:tree-data="(orgTreeData as any)"
|
||||
:field-names="{ children: 'children', title: 'name', key: 'id' }"
|
||||
@select="onOrgSelect"
|
||||
>
|
||||
@@ -212,9 +212,9 @@ const selectedOrgEmployees = computed(() => {
|
||||
return mockEmployees[selectedOrg.value.id] || []
|
||||
})
|
||||
|
||||
function onOrgSelect(keys: string[]) {
|
||||
function onOrgSelect(keys: Array<string | number>) {
|
||||
if (keys.length === 0) return
|
||||
const key = keys[0]
|
||||
const key = String(keys[0])
|
||||
const findOrg = (data: any[]): any => {
|
||||
for (const item of data) {
|
||||
if (item.id === key) return item
|
||||
@@ -236,11 +236,11 @@ function showAddOrgModal() {
|
||||
orgModalVisible.value = true
|
||||
}
|
||||
|
||||
function editOrg(id: string) {
|
||||
function editOrg(_id: string) {
|
||||
message.info('编辑功能开发中')
|
||||
}
|
||||
|
||||
function deleteOrg(id: string) {
|
||||
function deleteOrg(_id: string) {
|
||||
message.success('部门已删除')
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</p>
|
||||
<a-tree
|
||||
v-model:checkedKeys="selectedPermissions"
|
||||
:tree-data="permissionTree"
|
||||
:tree-data="(permissionTree as any)"
|
||||
:field-names="{ children: 'children', title: 'name', key: 'id' }"
|
||||
checkable
|
||||
@check="onPermissionChange"
|
||||
@@ -69,7 +69,7 @@
|
||||
<a-form-item label="权限">
|
||||
<a-tree
|
||||
v-model:checkedKeys="roleForm.permissions"
|
||||
:tree-data="permissionTree"
|
||||
:tree-data="(permissionTree as any)"
|
||||
:field-names="{ children: 'children', title: 'name', key: 'id' }"
|
||||
checkable
|
||||
/>
|
||||
@@ -82,8 +82,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import type { ColumnsType } from 'ant-design-vue/es/table'
|
||||
|
||||
const roleColumns = [
|
||||
const roleColumns: ColumnsType<any> = [
|
||||
{ title: '角色名称', dataIndex: 'name', key: 'name', width: 120 },
|
||||
{ title: '描述', dataIndex: 'description', key: 'description', width: 150, ellipsis: true },
|
||||
{ title: '用户数', dataIndex: 'userCount', key: 'userCount', width: 60 },
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
const app = getApp()
|
||||
|
||||
const request = (options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: app.globalData.baseUrl + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data || {},
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${app.globalData.token}`,
|
||||
...options.header,
|
||||
},
|
||||
success(res) {
|
||||
if (res.data.code === 0) {
|
||||
resolve(res.data.data)
|
||||
} else if (res.data.code === 401) {
|
||||
wx.navigateTo({ url: '/pages/profile/index' })
|
||||
reject(new Error('未登录'))
|
||||
} else {
|
||||
wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' })
|
||||
reject(new Error(res.data.msg))
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
wx.showToast({ title: '网络错误', icon: 'none' })
|
||||
reject(err)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { request }
|
||||
Reference in New Issue
Block a user