feat:基地档案

This commit is contained in:
wangzenghua 2025-01-23 09:27:53 +00:00
parent 93c928a191
commit 7902143658
18 changed files with 936 additions and 25 deletions

View File

@ -1,5 +1,5 @@
<template>
<div class="custom-table">
<div class="custom-page">
<avue-crud
v-model="state.form"
v-model:search="state.query"
@ -12,14 +12,43 @@
@search-change="searchChange"
@on-load="loadData"
@sort-change="sortChange"
@selection-change="selectionChange"
@row-click="rowClick"
@row-save="rowSave"
@row-update="rowUpdate"
@row-del="rowDel"
></avue-crud>
>
<template #menu-left>
<el-button type="danger" @click="onDel">批量删除</el-button>
</template>
<template #menu-right>
<el-button type="success" icon="download">导出</el-button>
</template>
<template #key-search="{ column }">
<el-input v-model="state.query[column.prop]" placeholder="请输入关键字" />
</template>
<template #pic="{ row, index }"> {{ row.pic }}-{{ index }} </template>
<template #pic-form="{ column, value }">
<!-- {{ column }}---{{ value }} -->
<el-upload
class="custom-form__uploader"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
:show-file-list="false"
:on-success="() => {}"
:before-upload="() => {}"
>
<img v-if="column.pic" :src="column.pic" class="custom-form__uploader__img" />
<el-icon v-else class="custom-form__uploader__icon"><Plus /></el-icon>
</el-upload>
</template>
</avue-crud>
</div>
</template>
<script setup name="table">
<script setup>
// https://avuejs.com/crud/crud-doc.html
import { reactive } from 'vue';
import { useApp } from '@/hooks';
@ -30,22 +59,38 @@ const state = reactive({
loading: false,
query: {},
form: {},
selection: [],
options: {
width: '100%',
// height: 300,
index: true,
selection: true,
selectionWidth: 100,
// selectionWidth: 100,
align: 'center',
headerAlign: 'center',
// border: true,
// stripe: true,
gridBtn: false,
// printBtn: true,
// excelBtn: true,
defaultSort: {
prop: 'datetime',
order: 'descending',
},
column: [
{
label: '关键字',
prop: 'key',
hide: false,
display: false,
search: true,
searchRules: [
{
required: true,
message: '请输入',
trigger: 'blur',
},
],
},
{
label: '姓名',
prop: 'name',
@ -103,7 +148,8 @@ const state = reactive({
dicFlag: true,
dicUrl: `https://cli.avuejs.com/api/area/getProvince`,
},
{ label: '地址', prop: 'address', overHidden: true, width: 200 },
{ label: '地址', prop: 'address', type: 'textarea', rows: 2, overHidden: true, span: 24, width: 200 },
{ label: '图片', prop: 'pic', type: 'upload', slot: true, formslot: true },
],
},
page: {
@ -118,10 +164,10 @@ const loadData = async () => {
state.loading = true;
await sleep(500);
state.data = [
{ name: '张三', sex: 1, datetime: '2014-12-01 12:00:00', address: '云南省昆明市经开区', code: '530128' },
{ name: '李四', sex: 2, datetime: '2025-01-01 12:34:00', address: '云南省昆明市经开区' },
{ name: '张三', sex: 1, datetime: '2024-12-01 14:00:00', address: '云南省昆明市经开区云南省昆明市经开区' },
{ name: '李四', sex: 2, datetime: '2025-12-01 15:00:00', address: '云南省昆明市经开区' },
{ id: 1, name: '张三', sex: 1, datetime: '2014-12-01 12:00:00', address: '云南省昆明市经开区', code: '530128' },
{ id: 2, name: '李四', sex: 2, datetime: '2025-01-01 12:34:00', address: '云南省昆明市经开区' },
{ id: 3, name: '张三', sex: 1, datetime: '2024-12-01 14:00:00', address: '云南省昆明市经开区云南省昆明市经开区' },
{ id: 4, name: '李四', sex: 2, datetime: '2025-12-01 15:00:00', address: '云南省昆明市经开区' },
];
state.page = {
total: 20,
@ -139,6 +185,12 @@ const sortChange = (val) => {
// app.$message.success(JSON.stringify(val));
};
//
const selectionChange = (rows) => {
console.log('selectionChange=', rows);
state.selection = rows;
};
//
const searchChange = (params, done) => {
console.log('search==', params);
@ -162,18 +214,30 @@ const rowClick = (row, event, column) => {
//
const rowSave = (row, done, loading) => {
// loading.value = true;
console.log('add=', row);
};
//
const rowUpdate = (row, index, done, loading) => {
// loading.value = true;
// done(row);
console.log('update=', row);
done(row);
};
//
const rowDel = (row, index, done) => {
console.log('del=', row);
done(row);
app
.$confirm(`您确定要删除?`, '确定删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
console.log('del=', row);
})
.catch(() => {});
};
const onDel = () => {};
</script>

View File

@ -0,0 +1,9 @@
root = true
[*.{js,jsx,ts,tsx,vue}]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = auto

View File

@ -0,0 +1,14 @@
*.sh
*.md
*.woff
*.ttf
.vscode
.idea
.husky
.local
dist
src/assets
node_modules
Dockerfile
stats.html
tailwind.config.js

View File

@ -0,0 +1,62 @@
/*
* @Descripttion: .eslintrc.cjs
* 在VSCode中安装ESLint插件编写过程中检测代码质量
* ESLint 代码质量校验相关配置
* 这里使用prettier作为代码格式化工具用ESLint做代码质检
* 相关配置使用下面extends扩展先做默认设置
* .prettierrc.cjs文件中配置好后格式化规则会以.prettierrc.cjs作为最终格式所以不建议在本文件中做代码格式化相关配置
* 相关prettier配置ESLint会默认加载为代码质检 格式化以prettier为主
* 在本配置文件中只做代码质量约束规范配置
* @Author: zenghua.wang
* @Date: 2022-09-22 15:53:58
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-03-22 10:19:39
*/
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint-config-prettier',
'eslint:recommended',
// 'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-recommended',
'plugin:vue/vue3-essential',
'plugin:prettier/recommended',
],
overrides: [
{
env: {
node: true,
},
files: ['.eslintrc.{js,cjs}'],
parserOptions: {
sourceType: 'script',
},
},
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
requireConfigFile: false,
parser: '@babel/eslint-parser',
// parser: '@typescript-eslint/parser',
},
plugins: ['vue', 'prettier'],
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly',
},
// 这里时配置规则的,自己看情况配置
rules: {
'prettier/prettier': 'error',
'no-debugger': 'off',
'no-unused-vars': 'off',
'vue/no-unused-vars': 'off',
'vue/multi-word-component-names': 'off',
},
};

View File

@ -0,0 +1,116 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

View File

@ -0,0 +1,20 @@
## OS
.DS_Store
node_modules
.idea
.editorconfig
package-lock.json
.npmrc
# Ignored suffix
*.log
*.md
*.svg
*.png
*ignore
## Local
## Built-files
.cache
dist

View File

@ -0,0 +1,52 @@
/*
* @Descripttion: .prettierrc.cjs
* 在VSCode中安装prettier插件 打开插件配置填写`.prettierrc.js` 将本文件作为其代码格式化规范
* 在本文件中修改格式化规则不会同时触发改变ESLint代码检查所以每次修改本文件需要重启VSCodeESLint检查才能同步代码格式化
* 需要相应的代码格式化规范请自行查阅配置下面为默认项目配置
* @Author: zenghua.wang
* @Date: 2022-09-22 15:53:58
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-24 19:22:25
*/
module.exports = {
// 一行最多多少个字符
printWidth: 150,
// 指定每个缩进级别的空格数
tabWidth: 2,
// 使用制表符而不是空格缩进行
useTabs: false,
// 在语句末尾是否需要分号
semi: true,
// 是否使用单引号
singleQuote: true,
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
quoteProps: 'as-needed',
// 在JSX中使用单引号而不是双引号
jsxSingleQuote: false,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>"默认none
trailingComma: 'es5',
// 在对象文字中的括号之间打印空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 在单独的箭头函数参数周围包括括号 always(x) => x \ avoidx => x
arrowParens: 'always',
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准 always\never\preserve
proseWrap: 'preserve',
// 指定HTML文件的全局空格敏感度 css\strict\ignore
htmlWhitespaceSensitivity: 'css',
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
//在 windows 操作系统中换行符通常是回车 (CR) 加换行分隔符 (LF),也就是回车换行(CRLF)
//然而在 Linux 和 Unix 中只使用简单的换行分隔符 (LF)。
//对应的控制字符为 "\n" (LF) 和 "\r\n"(CRLF)。auto意为保持现有的行尾
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'auto',
};

View File

@ -0,0 +1,17 @@
# .stylelintignore
# 旧的不需打包的样式库
*.min.css
# 其他类型文件
*.js
*.jpg
*.png
*.eot
*.ttf
*.woff
*.json
# 测试和打包目录
/dist/*
/node_modules/*
/src/assets/*

View File

@ -0,0 +1,131 @@
/*
* @Descripttion: .stylelintrc.cjs
* @Author: zenghua.wang
* @Date: 2022-09-22 15:53:58
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-24 18:49:26
*/
module.exports = {
root: true,
plugins: ['stylelint-order', 'stylelint-scss'],
extends: [
'stylelint-config-standard',
'stylelint-config-standard-scss',
'stylelint-config-prettier',
'stylelint-config-html/vue',
'stylelint-config-recommended-vue',
'stylelint-config-recommended-scss'
],
overrides: [
{
files: ['**/*.{html,vue}'],
customSyntax: 'postcss-html'
}
],
rules: {
indentation: 2,
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep', ':deep']
}
],
'number-leading-zero': 'always',
'no-descending-specificity': null,
'function-url-quotes': 'always',
'string-quotes': 'single',
'unit-case': null,
'color-hex-case': 'lower',
'color-hex-length': 'long',
'rule-empty-line-before': 'never',
'font-family-no-missing-generic-family-keyword': null,
'selector-type-no-unknown': null,
'block-opening-brace-space-before': 'always',
'at-rule-no-unknown': null,
'no-duplicate-selectors': null,
'property-no-unknown': null,
'no-empty-source': null,
'selector-class-pattern': null,
'keyframes-name-pattern': null,
'selector-pseudo-class-no-unknown': [true, { ignorePseudoClasses: ['global', 'deep'] }],
'function-no-unknown': null,
'order/properties-order': [
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'justify-content',
'align-items',
'float',
'clear',
'overflow',
'overflow-x',
'overflow-y',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'font-size',
'font-family',
'font-weight',
'border',
'border-style',
'border-width',
'border-color',
'border-top',
'border-top-style',
'border-top-width',
'border-top-color',
'border-right',
'border-right-style',
'border-right-width',
'border-right-color',
'border-bottom',
'border-bottom-style',
'border-bottom-width',
'border-bottom-color',
'border-left',
'border-left-style',
'border-left-width',
'border-left-color',
'border-radius',
'text-align',
'text-justify',
'text-indent',
'text-overflow',
'text-decoration',
'white-space',
'color',
'background',
'background-position',
'background-repeat',
'background-size',
'background-color',
'background-clip',
'opacity',
'filter',
'list-style',
'outline',
'visibility',
'box-shadow',
'text-shadow',
'resize',
'transition'
]
}
};

View File

@ -3,7 +3,7 @@
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-26 23:04:14
* @LastEditTime: 2025-01-23 15:59:17
-->
<template>
<el-breadcrumb class="layout-breadcrumb" separator="/">
@ -36,3 +36,9 @@ const handleLink = (item) => {
const matched = computed(() => route.matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false));
</script>
<style lang="scss" scoped>
.layout-breadcrumb {
@include flex-row();
align-items: center;
}
</style>

View File

@ -2,13 +2,13 @@
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang 1048523306@qq.com
* @LastEditTime: 2025-01-17 11:55:43
* @LastEditors: zenghua.wang
* @LastEditTime: 2025-01-23 09:40:41
-->
<template>
<div class="logo">
<!-- <img src="/images/logo.png" class="logo-picture" /> -->
<h2 v-show="!isCollapse" class="logo-title">管理系统</h2>
<h2 v-show="!isCollapse" class="logo-title">{{ VITE_APP_TITLE }}</h2>
</div>
</template>
@ -19,6 +19,8 @@ defineProps({
default: false,
},
});
const { VITE_APP_TITLE } = import.meta.env;
</script>
<style lang="scss" scoped>
@ -39,6 +41,8 @@ defineProps({
}
&-title {
height: 35px;
line-height: 35px;
color: $color-primary;
}
}

View File

@ -2,14 +2,15 @@
* @Description: router
* @Author: zenghua.wang
* @Date: 2023-06-20 11:48:41
* @LastEditors: zenghua.wang 1048523306@qq.com
* @LastEditTime: 2025-01-21 14:27:43
* @LastEditors: zenghua.wang
* @LastEditTime: 2025-01-23 16:13:59
*/
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
import { createRouter, createWebHistory } from 'vue-router';
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import Layout from '@/layouts/index.vue';
import resourceRouter from './modules/resource';
import plantingAndBreedingRouter from './modules/plantingAndBreeding';
const { VITE_APP_NAME } = import.meta.env;
@ -42,20 +43,20 @@ export const constantRoutes = [
],
},
...resourceRouter,
...plantingAndBreedingRouter,
];
/**
* @Title notFoundRouter(找不到路由)
*/
export const notFoundRouter = {
path: '/:pathMatch(.*)',
path: '/sub-government-affairs-service/:pathMatch(.*)',
name: 'notFound',
redirect: '/404',
};
const router = createRouter({
history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? `/${VITE_APP_NAME}/` : '/'), // history
// history: createWebHashHistory(), // hash
routes: constantRoutes,
});

View File

@ -0,0 +1,43 @@
import Layout from '@/layouts/index.vue';
import Views from '@/layouts/Views.vue';
export default [
{
path: '/plantingAndBreeding',
name: 'plantingAndBreeding',
component: Layout,
redirect: '/planting',
meta: { title: '种养殖管理', icon: 'icon-test' },
children: [
{
path: '/planting',
name: 'planting',
component: Views,
meta: { title: '种植档案', icon: 'Document' },
redirect: '/planting-archives',
children: [
{
path: '/planting-archives',
component: () => import('@/views/trace/planting/archives.vue'),
name: 'planting-archives',
meta: { title: '基地档案', icon: 'Document' },
},
{
path: '/planting-land2',
component: () => import('@/views/index.vue'),
name: 'planting-land2',
meta: { title: '种子档案', icon: 'Document' },
},
],
},
{
path: '/breeding',
name: 'breeding',
component: Views,
meta: { title: '农事管理', icon: 'Document' },
redirect: '/breeding-land',
children: [],
},
],
},
];

View File

@ -9,8 +9,8 @@
}
}
.custom-table {
padding: 10px;
.custom-page {
padding: 20px;
background: #fff;
border-radius: 10px;
}

View File

@ -0,0 +1,3 @@
<template>
<div>开发中</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div class="custom-table">
<div class="custom-page">
<avue-crud
v-model="state.form"
v-model:search="state.query"

View File

@ -0,0 +1,257 @@
<template>
<div class="custom-page">
<avue-crud
v-model="state.form"
v-model:search="state.query"
v-model:page="state.page"
:table-loading="state.loading"
:data="state.data"
:option="state.options"
@refresh-change="refreshChange"
@search-reset="searchChange"
@search-change="searchChange"
@row-save="rowSave"
@row-update="rowUpdate"
@row-del="rowDel"
>
<template #menu-left>
<el-button type="danger" icon="delete" @click="onDel">批量删除</el-button>
</template>
</avue-crud>
</div>
</template>
<script setup>
import { reactive } from 'vue';
import { useApp } from '@/hooks';
import { sleep } from '@/utils';
import Mock from 'mockjs';
const res = Mock.mock({
'data|20': [
{
id: '@increment(100000)',
name: '@ctitle(5,10)',
'area|100-1000': 100,
'status|1-2': 1,
location: '东经 98°53至 99°15北纬 23°27至 23°40F',
'type|1-2': 1,
'p1|1-2': 1,
'p2|100-1000': 100,
tag: '龙津河周边',
address: '耿马镇白塔社区',
createTime: '@datetime("yyyy-MM-dd HH:mm:ss")',
},
],
});
const app = useApp();
const state = reactive({
loading: false,
query: {},
form: {},
selection: [],
options: {
index: true,
indexLabel: '序号',
indexWidth: 80,
selection: true,
align: 'center',
headerAlign: 'center',
gridBtn: false,
addBtn: true,
addBtnText: '添加档案',
viewBtn: true,
editBtn: true,
delBtn: true,
gutter: 20,
labelWidth: 150,
column: [
{
label: '基地代码',
prop: 'id',
fixed: true,
display: false,
},
{
label: '基地名称',
prop: 'name',
fixed: true,
search: true,
width: 200,
rules: {
required: true,
message: '请输入',
trigger: 'blur',
},
},
{
label: '地址',
prop: 'address',
search: true,
// span: 24,
// rows: 4,
overHidden: true,
width: 200,
rules: {
required: true,
message: '请输入',
trigger: 'blur',
},
},
{
label: '使用状态',
prop: 'status',
type: 'select',
search: true,
dicData: [
{
label: '使用中',
value: 1,
},
{
label: '闲置',
value: 2,
},
],
rules: {
required: true,
message: '请输入',
trigger: 'blur',
},
},
{
label: '经纬度',
prop: 'location',
rules: {
required: true,
message: '请输入',
trigger: 'blur',
},
},
{
label: '土壤类型',
prop: 'type',
type: 'select',
dicData: [
{
label: '肥沃',
value: 1,
},
{
label: '贫瘠',
value: 2,
},
],
rules: {
required: true,
message: '请输入',
trigger: 'blur',
},
},
{
label: '气候条件',
prop: 'p1',
type: 'select',
dicData: [
{
label: '干燥少雨',
value: 1,
},
{
label: '湿润多雨',
value: 2,
},
],
rules: {
required: true,
message: '请输入',
trigger: 'blur',
},
},
{
label: '面积',
prop: 'area',
},
{
label: '海拔',
prop: 'p2',
},
{
label: '创建日期',
prop: 'createdTime',
type: 'datetimerange',
search: true,
rangeSeparator: '-',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
width: 200,
hide: true,
display: false,
},
],
},
page: {
total: 0,
currentPage: 1,
pageSize: 10,
},
data: [],
});
const loadData = async () => {
state.loading = true;
await sleep(500);
state.data = res.data;
state.page = {
total: 20,
currentPage: 1,
pageSize: 10,
};
state.loading = false;
};
loadData();
//
const searchChange = (params, done) => {
console.log('search==', params);
if (done) done();
state.query = params;
state.page.currentPage = 1;
loadData();
app.$message.success('搜索成功');
};
//
const refreshChange = () => {
loadData();
app.$message.success('刷新成功');
};
//
const rowSave = (row, done, loading) => {
console.log('add=', row);
};
//
const rowUpdate = (row, index, done, loading) => {
console.log('update=', row);
done(row);
};
//
const rowDel = (row, index, done) => {
app
.$confirm(`您确定要删除?`, '确定删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
console.log('del=', row);
})
.catch(() => {});
};
const onDel = () => {};
</script>

File diff suppressed because one or more lines are too long