采购商服务

This commit is contained in:
lzc 2025-04-02 17:32:13 +08:00
parent f637bec8f5
commit 33c0c01ff2
11 changed files with 646 additions and 19 deletions

View File

@ -42,11 +42,11 @@ const chearTime = () => {
};
onMounted(() => {
// startTime();
startTime();
});
onUnmounted(() => {
// chearTime();
chearTime();
});
defineExpose({

View File

@ -31,6 +31,10 @@ body {
div {
box-sizing: border-box;
}
--el-color-primary: #25bf82;
--el-color-primary-light-3: #45dda1;
--el-color-primary-light-5: #8cddbd;
.el-input {
--el-input-focus-border-color: #25bf82;
--el-input-focus-border: #25bf82;

View File

@ -0,0 +1,9 @@
import request from '@/utils/axios';
//云南省所有区域信息
export function getRegion(code) {
let codeVal = code ? code : '530000';
return request('/system/area/region?areaCode=' + codeVal, {
method: 'GET',
});
}

View File

@ -3,7 +3,7 @@ import { GenKey } from '@/config';
import { isEmpty, encode, decode } from '@/utils';
export const useUserStore = defineStore({
id: GenKey('USER_STATE'),
id: GenKey('userStore'),
state: () => ({
token: null,
userInfo: {},
@ -52,14 +52,14 @@ export const useUserStore = defineStore({
this.currentOrg = null;
this.orgList = [];
this.menus = [];
localStorage.removeItem(GenKey('USER_STATE'));
localStorage.removeItem(GenKey('userStore'));
},
clear() {
localStorage.removeItem(GenKey('USER_STATE'));
localStorage.removeItem(GenKey('userStore'));
},
},
persist: {
key: GenKey('USER_STATE'),
key: GenKey('userStore'),
storage: window.localStorage,
},
});

View File

@ -1,6 +1,7 @@
// color
$legacy-ie: 10;
$color-main:#25BF82;
$color-main-table-header:rgba(37,191,130,0.1);
$color-5a:#5A5A5A;
$color-000:#000;
$color-fff:#fff;

View File

@ -45,15 +45,20 @@ const errorHandler = async (error) => {
*/
publicAxios.interceptors.request.use(async (config) => {
const UserStore = useUserStore();
config.baseURL = config.isUpload ? VITE_APP_UPLOAD_API : VITE_APP_BASE_API;
switch (config.apisType) {
case 'upload': {
config.baseURL = VITE_APP_UPLOAD_API;
config.headers['Content-Type'] = config.uploadType;
break;
}
default: {
config.baseURL = VITE_APP_BASE_API;
}
}
if (UserStore.hasToken()) {
config.headers['fairies-auth-token'] = config.headers['fairies-auth-token'] ?? UserStore.token;
config.headers['fairies-org-id'] = UserStore.currentOrg;
config.headers['authorization'] = config.headers['authorization'] ?? UserStore.token;
config.headers['cache-control'] = 'no-cache';
config.headers.Pragma = 'no-cache';
if (config?.isUpload) {
config.headers['Content-Type'] = config.uploadType;
}
}
if (config.method === 'POST' || config.method === 'DELETE') {
config.headers.Accept = 'application/json';

View File

@ -31,14 +31,16 @@ router.beforeEach(async (to, from, next) => {
} else {
try {
const PermissionStore = usePermissionStore();
// 路由添加进去了没有及时更新 需要重新进去一次拦截
if (!PermissionStore.routes.length) {
// 获取权限列表进行接口访问 因为这里页面要切换权限
const accessRoutes = await PermissionStore.generateRoutes(userStore.roles);
accessRoutes.forEach((item) => router.addRoute(item)); // 动态添加访问路由表
next({ ...to, replace: true }); // 这里相当于push到一个页面 不在进入路由拦截
accessRoutes.forEach((item) => router.addRoute(item));
return next({ ...to, replace: true });
} else {
next(); // 如果不传参数就会重新执行路由拦截,重新进到这里
if (from.path.includes('/sub') && to.path.includes('/platform')) {
window.location.reload();
return;
}
next();
}
} catch (error) {
next(`/login?redirect=${to.path}`);

View File

@ -0,0 +1,97 @@
<template>
<div class="hot-goods-word-clould" :style="{ height: height }">
<custom-echart-word-cloud :chart-data="chartsData.valData" height="100%" :option="chartsData.option" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
const props = defineProps({
height: {
type: String,
default: '200px',
},
});
const chartsData = reactive({
option: {
backgroundColor: 'transparent',
tooltip: {
show: true,
textStyle: {
fontSize: '16',
color: '#3c3c3c',
},
backgroundColor: '#fff',
borderColor: '#ddd',
borderWidth: 1,
},
series: [],
},
valData: [
{
type: 'wordCloud',
//
gridSize: 22,
// circle cardioid diamond
// triangle-forward triangle star
shape: 'circle',
//
sizeRange: [10, 30],
//
rotationRange: [0, 0],
//
rotationStep: 90,
//
// maskImage: maskImage,
left: 'center',
top: 'center',
right: null,
bottom: null,
//
width: '90%',
//
height: '90%',
//
drawOutOfBound: false,
textStyle: {
color: function () {
const colors = ['#165DFF', '#6aca37', '#05a4b6', '#f93920', '#f0b114', '#ab1489', '#149cab', '#ab1414', '#27b30f', '#7e11b3', '#11b32e'];
return colors[parseInt(Math.random() * colors.length)];
},
emphasis: {
shadowBlur: 10,
shadowColor: '#2ac',
},
},
data: [
{ value: 11.7392043070835, name: '有机白菜' },
{ value: 9.23723855786, name: '土鸡蛋' },
{ value: 7.75434839431, name: '猪肉' },
{ value: 11.3865516372, name: '牛肉' },
{ value: 7.75434839431, name: '零添加' },
{ value: 5.83541244308, name: '原产地' },
{ value: 15.83541244308, name: '菠萝' },
{ value: 2.83541244308, name: '甘蔗' },
{ value: 5.83541244308, name: '土豆' },
{ value: 10.83541244308, name: '绿色' },
{ value: 5.83541244308, name: '美味' },
{ value: 5.83541244308, name: '特产' },
],
},
],
});
onMounted(() => {
if (chartsData.valData && chartsData.valData.length) {
chartsData.valData.forEach((m, index) => {
let num = 100;
m.value = (Number(m.value) + Math.random() + num).toFixed(2);
});
}
});
</script>
<style lang="scss" scoped>
.hot-goods-word-clould {
height: 100%;
}
</style>

View File

@ -0,0 +1,235 @@
<template>
<div class="market-charts" :style="{ height: height }">
<custom-echart-line-line :chart-data="chartsData.valData" height="100%" :option="chartsData.option" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue';
const props = defineProps({
height: {
type: String,
default: '200px',
},
});
const legendData = reactive(['苹果', '小麦', '白菜']);
let linearColors = reactive([
[
{ offset: 0, color: 'rgba(15, 155, 179,0.1)' },
{ offset: 0.6, color: 'rgba(15, 155, 179,0.5)' },
{ offset: 1, color: 'rgba(15, 155, 179,1)' },
],
[
{ offset: 0, color: 'rgba(106, 202, 55,0.1)' },
{ offset: 0.6, color: 'rgba(106, 202, 55,0.5)' },
{ offset: 1, color: 'rgba(106, 202, 55,1)' },
],
[
{ offset: 0, color: 'rgba(141, 76, 181,0.1)' },
{ offset: 0.6, color: 'rgba(141, 76, 181,0.5)' },
{ offset: 1, color: 'rgba(141, 76, 181,1)' },
],
]);
const lineColors = reactive(['#0f9bb3', '#6aca37', '#8d4cb5']);
const currentYear = ref(new Date().getFullYear() + 1);
const xPoint = computed(() => {
let list = [];
console.info('111', currentYear.value);
let minYear = currentYear.value - 10;
for (let i = minYear; i < currentYear.value; i++) {
list.push(i);
}
return list;
});
let numList = reactive([10, 100, 80]);
const dataNum = (num) => {
let data = [];
if (xPoint.value && xPoint.value.length && xPoint.value.length > 0 && num) {
xPoint.value.forEach((n) => {
let numVal = Number(Number(n) * 0.1 + num + Math.random() * 100).toFixed(2);
data.push(numVal);
});
}
return data;
};
const series = () => {
let list = [];
if (legendData && legendData.length && legendData.length > 0) {
// console.info('legendData', legendData);
legendData.forEach((m, index) => {
let val = {
name: m,
type: 'line',
symbol: 'none', //
smooth: true,
lineStyle: {
normal: {
width: 1,
color: lineColors[index], // 线
},
},
areaStyle: {
normal: {
//线4x0,y0,x2,y2(0~1);true
color: {
type: 'linear', // 线
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: linearColors[index],
global: false, // false
},
},
},
data: [],
};
val.data = dataNum(numList[index]);
list.push(val);
});
}
return list;
};
const chartsData = reactive({
option: {
backgroundColor: 'transparent',
grid: {
left: 20,
right: 20,
bottom: '5%',
top: '20%',
containLabel: true,
},
tooltip: {
trigger: 'axis',
borderWidth: 1,
borderColor: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(85, 149, 233, .6)', // 0%
},
{
offset: 1,
color: 'rgba(85, 149, 233, 0)', // 100%
},
],
global: false, // false
},
padding: [0, 8, 0, 8],
textStyle: {
color: '#fff',
},
axisPointer: {
lineStyle: {
type: 'dashed',
color: 'rgba(255, 255, 255, .6)',
},
},
extraCssText: 'box-shadow: 2px 2px 16px 1px rgba(0, 39, 102, 0.16)',
formatter: function (params) {
let content = `<div style='font-size: 14px; color: #666;text-align:left'>${params[0].name}</div>`;
if (Array.isArray(params)) {
for (let i = 0; i < params.length; i++) {
content += `
<div style='display: flex; align-items: center;color: #666;'>
<div style='width: 10px; height: 10px;border-radius:4px;background: ${params[i].color}; margin-right: 8px;'></div>
<div style='font-size: 12px; margin-right: 8px;'>${params[i].seriesName}</div>
<div style='font-size: 14px;'>${params[i].value}</div>
</div>
`;
}
}
return content;
},
},
legend: {
show: true,
data: Array.from(legendData),
left: 'center', // 10%
top: '0', //
itemWidth: 15, //
itemHeight: 8, //
textStyle: {
fontSize: 10, //
color: '#333', //
},
},
xAxis: {
type: 'category',
boundaryGap: false,
axisLabel: {
interval: 0, // 1
textStyle: {
color: '#666',
fontStyle: 'normal',
fontSize: 10,
},
},
axisTick: {
show: false,
},
axisLine: {
// 线
lineStyle: {
color: 'rgba(77, 128, 254, 1)',
},
},
splitLine: {
show: false,
},
data: xPoint.value,
},
yAxis: {
type: 'value',
name: ' ',
nameTextStyle: {
color: '#666',
fontSize: 14,
padding: [0, 0, 0, -30],
},
axisLabel: {
interval: 0, // 1
textStyle: {
color: '#666',
fontStyle: 'normal',
fontSize: 12,
},
},
axisTick: {
show: false,
},
axisLine: {
// 线
lineStyle: {
color: 'rgba(77, 128, 254, 0.2)',
},
},
splitLine: {
show: false,
},
},
series: series(),
},
});
onMounted(() => {});
</script>
<style lang="scss" scoped>
.market-charts-charts {
height: 100%;
}
</style>

View File

@ -0,0 +1,151 @@
<template>
<div class="purchaser-popup-warp">
<el-dialog v-model="isShow" title="采购报价" width="800" :before-close="doCancel">
<el-form ref="ruleFormRef" :model="ruleForm" status-icon :rules="rules" label-width="auto" class="custom-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="商品分类" prop="typeId">
<el-cascader v-model="ruleForm.typeId" :options="treeList" :props="treeOption" clearable :placeholder="'请选择分类'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收购价格" prop="price">
<el-input-number v-model="ruleForm.price" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采购商品" prop="goods">
<el-input v-model="ruleForm.goods" style="width: 200px" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收购数量" prop="num">
<el-input-number v-model="ruleForm.num" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收货地址" prop="addr">
<el-input v-model="ruleForm.addr" :rows="2" type="textarea" :placeholder="'收货地址'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label=" " prop="isPickup">
<el-radio-group v-model="ruleForm.isPickup">
<el-radio v-for="(n, index) in pickupOptions" :key="index" :value="n.value">{{ n.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采购说明" prop="remark">
<el-input v-model="ruleForm.remark" :rows="2" type="textarea" :placeholder="'采购说明'" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="doCancel">取消</el-button>
<el-button type="primary" @click="doSure"> 确定 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, watch } from 'vue';
import { isEmpty, getAssetsFile } from '@/utils';
import { useRoute, useRouter } from 'vue-router';
import { fa } from 'element-plus/es/locale/index.mjs';
const route = useRoute();
const router = useRouter();
const props = defineProps({
list: {
type: Array,
default: () => {
return [];
},
},
});
let treeList = reactive([]);
let ruleForm = reactive({
typeId: '',
price: '',
goods: '',
num: '',
addr: '',
isPickup: 1,
remark: '',
});
const pickupOptions = reactive([
{ label: '可上门收货', value: 1 },
{ label: '不上门收货', value: 0 },
]);
let ruleFormRef = ref(null);
const treeOption = ref({
children: 'children',
label: 'title',
});
watch(
() => props.list,
() => {
treeList = props.list;
},
{
immediate: true,
}
);
let isShow = ref(false);
const doShow = () => {
isShow.value = true;
};
const doHide = () => {
isShow.value = false;
};
const doCancel = () => {
doHide();
};
const doSure = () => {
doHide();
};
const rules = reactive({
typeId: [{ required: true, message: '请选择商品分类', trigger: ['blur', 'change'] }],
price: [{ required: true, message: '请输入采购价格', trigger: ['blur', 'change'] }],
goods: [{ required: true, message: '请输入采购商品', trigger: ['blur', 'change'] }],
num: [{ required: true, message: '请输入采购数量', trigger: ['blur', 'change'] }],
addr: [{ required: true, message: '请填入详细地址', trigger: ['blur', 'change'] }],
isPickup: [{ required: true, message: '请选择是否上门取货', trigger: ['blur', 'change'] }],
remark: [{ required: false, message: '请输入补充说明', trigger: ['blur', 'change'] }],
});
defineExpose({
doShow,
doHide,
});
</script>
<style lang="scss" scoped>
.purchaser-popup-warp {
}
.custom-form {
::v-deep() {
.el-form-item__label {
font-size: 18px !important;
color: $color-999 !important;
font-weight: 400 !important;
}
.el-radio__label {
font-size: 18px;
color: $color-333;
}
}
}
</style>

View File

@ -5,22 +5,61 @@
<el-row class="purchaser-top">
<el-col :span="12">
<div class="purchaser-top-title">行情动态</div>
<div class="purchaser-charts"></div>
<div class="purchaser-charts">
<marketCharts></marketCharts>
</div>
</el-col>
<el-col :span="12">
<div class="purchaser-top-title">近7天热门产品</div>
<div class="purchaser-charts"></div>
<div class="purchaser-charts">
<hotGoodsWordClould></hotGoodsWordClould>
</div>
</el-col>
</el-row>
<filtertop :list="treeList"></filtertop>
<div class="hot-list-warp">
<el-row :gutter="10">
<el-col :span="8" align="left">
<el-cascader :options="typeTree" :props="treeOption" clearable :placeholder="'请选择地区'" />
</el-col>
<el-col :span="16" align="left">
<el-radio-group v-model="filter.time">
<el-radio v-for="(n, index) in dateList" :key="index" :value="n.value">{{ n.label }}</el-radio>
</el-radio-group>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-table :data="tableData" style="width: 100%" height="320">
<el-table-column prop="title" label="采购品种" />
<el-table-column prop="region" label="收货地" />
<el-table-column prop="buyer" label="采购方" />
<el-table-column prop="buyNum" label="采购量(kg)" />
<el-table-column prop="buyer" label="采购时间" />
<el-table-column fixed="right" label="操作">
<template #default>
<el-button link type="primary" size="small" @click="handleClick"> 去报价 </el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</div>
</template>
</common>
<purchaserPopup ref="popupQuote" :list="treeList"></purchaserPopup>
</div>
</template>
<script setup name="ecommerce">
import common from './components/common.vue';
import filtertop from './components/filtertop.vue';
import hotGoodsWordClould from './components/hotGoodsWordClould.vue';
import marketCharts from './components/marketCharts.vue';
import purchaserPopup from './components/purchaserPopup.vue';
import { ref, reactive, onMounted, watch, computed } from 'vue';
import { getRegion } from '@/apis/index';
let treeList = reactive([
{
@ -132,6 +171,53 @@ let treeList = reactive([
],
},
]);
let popupQuote = ref(null);
const typeTree = ref([]);
const treeOption = ref({
children: 'areaChildVOS',
label: 'areaName',
});
const getTree = () => {
console.info('getTree');
getRegion().then((res) => {
if (res.code === 200) {
const { code, data } = res;
typeTree.value = data;
console.info('区域信息', typeTree.value);
}
});
};
const handleClick = () => {
popupQuote.value && popupQuote.value.doShow();
};
const dateList = reactive([
{ label: '近7天发布', value: 7 },
{ label: '近30天发布', value: 30 },
]);
let filter = ref({
region: '',
time: 7,
});
let tableData = reactive([
{ title: '西红柿', region: '耿马镇', buyer: '盛源农业', buyNum: 5000, time: '2025-04-02 12:25:36' },
{ title: '白菜', region: '孟定镇', buyer: '星悦农业', buyNum: 6000, time: '2025-04-01 12:25:36' },
{ title: '西蓝花', region: '孟勇镇', buyer: '盛源农业', buyNum: 7000, time: '2025-04-02 13:25:36' },
{ title: '鸡蛋', region: '勐简乡', buyer: '上好佳农业', buyNum: 8000, time: '2025-04-02 12:25:36' },
{ title: '牛肉', region: '四排山乡', buyer: '尚嘉农业', buyNum: 5000, time: '2025-03-31 12:25:36' },
{ title: '氮肥', region: '大兴乡', buyer: '信誉农资', buyNum: 6000, time: '2025-04-02 12:25:36' },
{ title: '白菜种子', region: '贺派乡', buyer: '佳佳农业', buyNum: 5000, time: '2025-04-02 12:25:36' },
{ title: '西红柿种子', region: '勐撒镇', buyer: '佳佳农业', buyNum: 8000, time: '2025-04-02 12:25:36' },
]);
onMounted(() => {
console.info('onMounted');
getTree();
});
</script>
<style lang="scss" scoped>
.purchaser-index-warp {
@ -152,5 +238,42 @@ let treeList = reactive([
width: 100%;
}
}
.hot-list-warp {
background: $color-fff;
padding: 16px;
border-radius: 16px;
margin: 16px 0;
::v-deep() {
thead {
border-radius: 16px;
overflow: hidden;
}
thead th {
background: $color-main-table-header;
color: $color-999 !important;
border: none !important;
padding: 16px 0;
}
thead th:first-child {
border-top-left-radius: 16px;
border-bottom-left-radius: 16px;
}
thead th:last-child {
border-top-right-radius: 16px;
border-bottom-right-radius: 16px;
}
table {
margin-top: 16px !important;
}
tbody td {
border: none !important;
}
.el-table__inner-wrapper::before {
display: none !important;
}
}
}
}
</style>