This commit is contained in:
木瓜一块八 2025-08-12 19:56:40 +08:00
parent 6ce0ebc210
commit bad11ab8fb
18 changed files with 3695 additions and 955 deletions

View File

@ -48,3 +48,44 @@ export function reqGetSupplyAndDemandSquareList(data) {
data data
}) })
} }
//获取商品详情
export function reqGetProductDetail(data) {
return request({
url: '/product/publish_product_search_detail.dspy',
method: 'post',
headers: { 'Content-Type': 'application/json' },
data
})
}
//提交审批 ///user/enterprise_audit_info_add.dspy
export function reqApproveUser(data){
return request({
url: '/user/enterprise_audit_info_add.dspy',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
data
})
}
//获取菜单ncmatch菜单 menu
export function reqNcMatchMenu(data){
return request({
url: '/product/homepage_category_tree_search.dspy',
method: 'post',
headers: { 'Content-Type': 'application/json' },
data
})
}
//运营查找商品|需求
export function reqSearchByMangement(data){
return request({
url: '/product/publish_product_search.dspy',
method: 'post',
headers: { 'Content-Type': 'application/json' },
data
})
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
const state = {
showProductDetail: false,
// 产品详情
productDetail: { },
//是否是审核
isAudit: false,
}
const mutations = {
SETPRODUCTDETAIL(state, productDetail) {
state.productDetail = productDetail
},
GETPRODUCTDETAIL(state) {
return state.productDetail
},
SHOWPRODUCTDETAIL(state, showProductDetail) {
state.showProductDetail = showProductDetail
},
GETSHOWPRODUCTDETAIL(state) {
return state.showProductDetail
}
}
const actions = {
}
const getters = {}
export default {
state,
actions,
mutations,
getters
}

View File

@ -0,0 +1,945 @@
<template>
<div class="nc-approve-container">
<el-card class="approve-card">
<div slot="header" class="card-header">
<span class="header-title">
<i class="el-icon-document"></i>
信息审核
</span>
<el-button type="primary" size="small" @click="handleSubmit" :loading="submitLoading"
:disabled="!canSubmit">
<i class="el-icon-check"></i>
提交审批
</el-button>
</div>
<el-form ref="approveForm" :model="approveForm" :rules="formRules" label-width="120px" class="approve-form">
<!-- 基本信息 -->
<el-divider content-position="left">
<i class="el-icon-info"></i>
基本信息
</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="账号类型" prop="account_type">
<el-select v-model="approveForm.account_type" placeholder="请选择账号类型" style="width: 100%">
<el-option label="企业账号" value="enterprise"></el-option>
<el-option label="个人账号" value="personal"></el-option>
<el-option label="政府机构" value="government"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司名称" prop="company_name">
<el-input v-model="approveForm.company_name" placeholder="请输入公司名称" maxlength="100"
show-word-limit>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="执照号码" prop="license_number">
<el-input v-model="approveForm.license_number" placeholder="请输入营业执照号码" maxlength="50">
</el-input>
</el-form-item>
</el-col>
<!-- <el-col :span="12">
<el-form-item label="审核状态" prop="audit_status">
<el-tag
:type="getStatusType(approveForm.audit_status)"
size="medium">
{{ getStatusText(approveForm.audit_status) }}
</el-tag>
</el-form-item>
</el-col> -->
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="办公地址" prop="office_address">
<el-input v-model="approveForm.office_address" placeholder="请输入办公地址" maxlength="200"
show-word-limit>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="注册地址" prop="registered_address">
<el-input v-model="approveForm.registered_address" placeholder="请输入注册地址" maxlength="200"
show-word-limit>
</el-input>
</el-form-item>
</el-col>
</el-row>
<!-- 联系信息 -->
<el-divider content-position="left">
<i class="el-icon-phone"></i>
联系信息
</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系人姓名" prop="contact_name">
<el-input v-model="approveForm.contact_name" placeholder="请输入联系人姓名" maxlength="20">
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="固定电话" prop="telephone">
<el-input v-model="approveForm.telephone" placeholder="请输入固定电话" maxlength="20">
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="移动电话" prop="mobile_phone">
<el-input v-model="approveForm.mobile_phone" placeholder="请输入移动电话" maxlength="11">
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱地址" prop="email">
<el-input v-model="approveForm.email" placeholder="请输入邮箱地址" type="email" maxlength="50">
</el-input>
</el-form-item>
</el-col>
</el-row>
<!-- 营业执照上传 -->
<el-divider content-position="left">
<i class="el-icon-picture"></i>
营业执照
</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="营业执照正本" prop="license_original_img">
<el-upload
class="license-uploader"
action="#"
:http-request="handleLicenseUpload"
:show-file-list="false"
:before-upload="beforeLicenseUpload"
accept="image/*">
<img v-if="approveForm.license_original_img"
:src="approveForm.license_original_img"
class="license-image">
<i v-else class="el-icon-plus license-uploader-icon"></i>
</el-upload>
<div class="upload-tip">
<i class="el-icon-info"></i>
支持 JPGPNGGIF 格式文件大小不超过 5MB
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="营业执照副本" prop="license_copy_img">
<el-upload
class="license-uploader"
action="#"
:http-request="handleLicenseCopyUpload"
:show-file-list="false"
:before-upload="beforeLicenseUpload"
accept="image/*">
<img v-if="approveForm.license_copy_img"
:src="approveForm.license_copy_img"
class="license-image">
<i v-else class="el-icon-plus license-uploader-icon"></i>
</el-upload>
<div class="upload-tip">
<i class="el-icon-info"></i>
支持 JPGPNGGIF 格式文件大小不超过 5MB
</div>
</el-form-item>
</el-col>
</el-row>
<!-- 操作按钮 -->
<el-form-item class="form-actions">
<el-button @click="handleReset" size="medium">
<i class="el-icon-refresh"></i>
重置
</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading" :disabled="!canSubmit"
size="medium">
<i class="el-icon-check"></i>
提交审批
</el-button>
<el-button type="success" @click="handlePreview" size="medium">
<i class="el-icon-view"></i>
预览
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 预览对话框 -->
<el-dialog title="审批信息预览" :visible.sync="previewVisible" top="3vh" width="60%" :before-close="handlePreviewClose">
<div class="preview-content">
<el-descriptions :column="2" border>
<el-descriptions-item label="账号类型">
{{ getAccountTypeText(approveForm.account_type) }}
</el-descriptions-item>
<el-descriptions-item label="公司名称">
{{ approveForm.company_name }}
</el-descriptions-item>
<el-descriptions-item label="执照号码">
{{ approveForm.license_number }}
</el-descriptions-item>
<!-- <el-descriptions-item label="审核状态1">
<el-tag :type="getStatusType(approveForm.audit_status)">
{{ getStatusText(approveForm.audit_status) }}
</el-tag>
</el-descriptions-item> -->
<el-descriptions-item label="办公地址">
{{ approveForm.office_address }}
</el-descriptions-item>
<el-descriptions-item label="注册地址">
{{ approveForm.registered_address }}
</el-descriptions-item>
<el-descriptions-item label="联系人">
{{ approveForm.contact_name }}
</el-descriptions-item>
<el-descriptions-item label="联系电话">
{{ approveForm.mobile_phone }}
</el-descriptions-item>
<el-descriptions-item label="邮箱地址">
{{ approveForm.email }}
</el-descriptions-item>
</el-descriptions>
<div class="preview-images">
<div class="preview-image" v-if="approveForm.license_original_img">
<h4>营业执照正本</h4>
<img :src="approveForm.license_original_img" alt="营业执照正本" class="preview-license-img">
</div>
<div class="preview-image" v-if="approveForm.license_copy_img">
<h4>营业执照副本</h4>
<img :src="approveForm.license_copy_img" alt="营业执照副本" class="preview-license-img">
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="previewVisible = false">关闭</el-button>
<el-button type="primary" @click="handleSubmit">确认提交</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import {reqApproveUser } from '@/api/ncmatch/index'
export default {
name: "ncApprove",
data() {
return {
approveForm: {
account_type: "", //
company_name: '', //
license_number: '', //
license_original_img: '', //
license_copy_img: '', //
license_original_file: null, //
license_copy_file: null, //
office_address: '', //
registered_address: '', //
contact_name: '', //
telephone: '', //
mobile_phone: '', //
email: '', //
audit_status: 'pending', //
},
formRules: {
account_type: [
{ required: true, message: '请选择账号类型', trigger: 'change' }
],
company_name: [
{ required: true, message: '请输入公司名称', trigger: 'blur' },
{ min: 2, max: 100, message: '公司名称长度在 2 到 100 个字符', trigger: 'blur' }
],
license_number: [
{ required: true, message: '请输入执照号码', trigger: 'blur' }
],
contact_name: [
{ required: true, message: '请输入联系人姓名', trigger: 'blur' }
],
mobile_phone: [
{ required: true, message: '请输入移动电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
license_original_img: [
{
required: true,
validator: (rule, value, callback) => {
if (!this.approveForm.license_original_file) {
callback(new Error('请上传营业执照正本'));
} else {
callback();
}
},
trigger: 'change'
}
],
license_copy_img: [
{
required: true,
validator: (rule, value, callback) => {
if (!this.approveForm.license_copy_file) {
callback(new Error('请上传营业执照副本'));
} else {
callback();
}
},
trigger: 'change'
}
]
},
submitLoading: false,
previewVisible: false
}
},
computed: {
canSubmit() {
return this.approveForm.account_type &&
this.approveForm.company_name &&
this.approveForm.license_number &&
this.approveForm.contact_name &&
this.approveForm.mobile_phone &&
this.approveForm.email &&
this.approveForm.license_original_file &&
this.approveForm.license_copy_file;
}
},
methods: {
//
getAccountTypeText(type) {
const typeMap = {
'enterprise': '企业账号',
'personal': '个人账号',
'government': '政府机构'
};
return typeMap[type] || '未知';
},
//
getStatusType(status) {
const statusMap = {
'pending': 'warning',
'approved': 'success',
'rejected': 'danger',
'processing': 'info'
};
return statusMap[status] || 'info';
},
//
getStatusText(status) {
const statusMap = {
'pending': '待审核',
'approved': '已通过',
'rejected': '已拒绝',
'processing': '审核中'
};
return statusMap[status] || '未知';
},
//
beforeLicenseUpload(file) {
const isImage = file.type.startsWith('image/');
const isLt5M = file.size / 1024 / 1024 < 5;
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
if (!isImage || !allowedTypes.includes(file.type)) {
this.$message.error('只能上传 JPG、PNG、GIF 格式的图片文件!');
return false;
}
if (!isLt5M) {
this.$message.error('图片大小不能超过 5MB!');
return false;
}
return true;
},
//
handleLicenseUpload(options) {
const file = options.file;
//
this.approveForm.license_original_file = file;
//
const reader = new FileReader();
reader.onload = (e) => {
this.approveForm.license_original_img = e.target.result;
this.$message.success('营业执照正本上传成功');
};
reader.readAsDataURL(file);
},
//
handleLicenseCopyUpload(options) {
const file = options.file;
//
this.approveForm.license_copy_file = file;
//
const reader = new FileReader();
reader.onload = (e) => {
this.approveForm.license_copy_img = e.target.result;
this.$message.success('营业执照副本上传成功');
};
reader.readAsDataURL(file);
},
//
handleSubmit() {
this.$refs.approveForm.validate((valid) => {
if (valid) {
this.submitLoading = true;
let formData = new FormData();
//
const basicFields = [
'account_type', 'company_name', 'license_number',
'office_address', 'registered_address', 'contact_name',
'telephone', 'mobile_phone', 'email', 'audit_status'
];
basicFields.forEach(field => {
if (this.approveForm[field]) {
formData.append(field, this.approveForm[field]);
}
});
//
if (this.approveForm.license_original_file) {
formData.append('license_original_img', this.approveForm.license_original_file);
}
if (this.approveForm.license_copy_file) {
formData.append('license_copy_img', this.approveForm.license_copy_file);
}
reqApproveUser(formData).then(res => {
this.submitLoading = false;
if (res.status) {
this.$message.success('审批信息提交成功!');
this.approveForm.audit_status = 'processing';
} else {
this.$message.error(res.message || '提交失败,请重试');
}
}).catch(error => {
this.submitLoading = false;
this.$message.error('网络错误,请重试');
console.error('提交失败:', error);
});
} else {
this.$message.error('请完善必填信息');
}
});
},
//
handleReset() {
this.$refs.approveForm.resetFields();
this.approveForm.license_original_img = '';
this.approveForm.license_copy_img = '';
this.approveForm.license_original_file = null;
this.approveForm.license_copy_file = null;
this.approveForm.audit_status = 'pending';
this.$message.info('表单已重置');
},
//
handlePreview() {
if (this.canSubmit) {
this.previewVisible = true;
} else {
this.$message.warning('请先完善必填信息');
}
},
//
handlePreviewClose(done) {
done();
},
//
getFileInfo(file) {
if (!file) return null;
return {
name: file.name,
size: file.size,
type: file.type,
lastModified: file.lastModified
};
}
}
}
</script>
<style lang="scss" scoped>
.nc-approve-container {
min-height: 100vh;
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="50" cy="50" r="1" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
opacity: 0.3;
pointer-events: none;
}
.approve-card {
// max-width: 1200px;
margin: 0 45px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 16px;
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.1),
0 8px 16px rgba(0, 0, 0, 0.05);
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
overflow: hidden;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 32px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
.header-title {
font-size: 20px;
font-weight: 700;
color: #2c3e50;
display: flex;
align-items: center;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
i {
margin-right: 12px;
color: #667eea;
font-size: 24px;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
}
.el-button {
border-radius: 8px;
font-weight: 600;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
}
}
.approve-form {
// padding: 32px;
.el-divider {
margin: 40px 0 24px 0;
.el-divider__text {
font-size: 18px;
font-weight: 700;
color: #2c3e50;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
padding: 0 20px;
i {
margin-right: 10px;
color: #667eea;
font-size: 20px;
}
}
&::before,
&::after {
background: linear-gradient(90deg, transparent, #667eea, transparent);
}
}
.el-form-item {
margin-bottom: 24px;
.el-form-item__label {
font-weight: 600;
color: #2c3e50;
font-size: 14px;
}
.el-input,
.el-select {
.el-input__inner {
border-radius: 8px;
border: 2px solid #e1e8ed;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.9);
&:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
&:hover {
border-color: #b8c5d6;
}
}
}
.el-tag {
border-radius: 6px;
font-weight: 600;
padding: 8px 16px;
font-size: 13px;
border: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
}
.license-uploader {
border: 2px dashed #d1d9e0;
border-radius: 12px;
cursor: pointer;
position: relative;
overflow: hidden;
width:150px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
border-color: #667eea;
background: linear-gradient(135deg, #f0f4ff 0%, #e8f0ff 100%);
// transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.2);
}
.license-uploader-icon {
font-size: 28px;
color: #667eea;
width: 100%;
height: 160px;
line-height: 160px;
text-align: center;
transition: all 0.3s ease;
}
.license-image {
width: 100%;
height: 160px;
display: block;
object-fit: cover;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
.upload-tip {
margin-top: 12px;
font-size: 13px;
color: #6c757d;
background: rgba(102, 126, 234, 0.1);
padding: 4px 8px;
border-radius: 6px;
border-left: 3px solid #667eea;
i {
margin-right: 6px;
color: #667eea;
}
}
.form-actions {
margin-top: 40px;
text-align: center;
border-top: 2px solid #f1f3f4;
padding-top: 30px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
margin: 40px -32px -32px -32px;
padding: 30px 32px;
.el-button {
margin: 0 12px;
border-radius: 8px;
font-weight: 600;
padding: 12px 24px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}
&.el-button--primary {
background: linear-gradient(135deg, #667eea, #764ba2);
border: none;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
&:hover {
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
}
&.el-button--success {
background: linear-gradient(135deg, #56ab2f, #a8e6cf);
border: none;
box-shadow: 0 4px 12px rgba(86, 171, 47, 0.3);
&:hover {
box-shadow: 0 8px 20px rgba(86, 171, 47, 0.4);
}
}
}
}
}
}
.preview-content {
.preview-images {
margin-top: 24px;
display: flex;
gap: 20px;
flex-wrap: wrap;
.preview-image {
flex: 1;
min-width: 300px;
text-align: center;
h4 {
margin-bottom: 16px;
color: #2c3e50;
font-weight: 600;
font-size: 16px;
}
.preview-license-img {
max-width: 100%;
max-height: 250px;
border: 2px solid #e1e8ed;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
}
.el-descriptions {
.el-descriptions__body {
.el-descriptions__table {
.el-descriptions__cell {
padding: 16px 20px;
font-size: 14px;
}
}
}
}
}
}
//
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.nc-approve-container {
animation: fadeInUp 0.6s ease-out;
}
//
@media (max-width: 768px) {
.nc-approve-container {
padding: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
.approve-card {
border-radius: 12px;
margin: 0 5px;
.card-header {
flex-direction: column;
gap: 15px;
padding: 20px;
.header-title {
font-size: 18px;
}
}
.approve-form {
padding: 20px;
.el-divider {
margin: 30px 0 20px 0;
.el-divider__text {
font-size: 16px;
}
}
.el-form-item {
margin-bottom: 20px;
}
.license-uploader {
width: 100%;
height: 120px;
.license-uploader-icon {
width: 100%;
height: 120px;
line-height: 120px;
font-size: 24px;
}
.license-image {
width: 100%;
height: 120px;
}
}
.preview-content {
.preview-images {
flex-direction: column;
gap: 15px;
.preview-image {
min-width: auto;
}
}
}
.form-actions {
margin: 30px -20px -20px -20px;
padding: 20px;
.el-button {
margin: 5px;
padding: 10px 20px;
font-size: 14px;
}
}
}
}
}
}
//
@media (prefers-color-scheme: dark) {
.nc-approve-container {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
.approve-card {
background: rgba(44, 62, 80, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
.card-header {
background: linear-gradient(135deg, #34495e 0%, #2c3e50 100%);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
.header-title {
color: #ecf0f1;
}
}
.approve-form {
.el-divider__text {
color: #ecf0f1;
}
.el-form-item__label {
color: #ecf0f1;
}
.el-input__inner {
background: rgba(52, 73, 94, 0.9);
border-color: #34495e;
color: #ecf0f1;
&:focus {
border-color: #667eea;
}
}
.license-uploader {
background: linear-gradient(135deg, #34495e 0%, #2c3e50 100%);
border-color: #34495e;
&:hover {
background: linear-gradient(135deg, #3a5a78 0%, #34495e 100%);
}
}
.form-actions {
background: linear-gradient(135deg, #34495e 0%, #2c3e50 100%);
border-top-color: rgba(255, 255, 255, 0.1);
}
}
}
}
}
//
.el-loading-mask {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(5px);
}
//
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 4px;
&:hover {
background: linear-gradient(135deg, #5a6fd8, #6a4190);
}
}
</style>

View File

@ -0,0 +1,350 @@
<template>
<div>
<div>
<ul class="search-box">
<li>
<span class="search-label">创建日期:</span>
<el-date-picker size="mini" style="width: 220px;" v-model="searchDate" type="datetimerange"
align="right" start-placeholder="开始日期" end-placeholder="结束日期"
:default-time="['00:00:00', '23:59:59']" format="yyyy-MM-dd" value-format="yyyy-MM-dd"
:picker-options="pickerOptions">
</el-date-picker>
</li>
<li>
<el-input size="mini" v-model="searchData.keyword" placeholder="请输入商品名称或关键词" clearable></el-input>
</li>
<li>
<el-button size="mini" @click="resetSearch">重置</el-button>
<el-button type="primary" @click="getTableData" size="mini">查询</el-button>
</li>
</ul>
</div>
<div class="table-box">
<div style="margin-bottom: 10px;">
<el-radio-group v-model="searchData.radioType" class="radio-group" size="mini"
@change="handleRadioChange">
<el-radio-button v-for="item in radioOptions" :key="item.value" :label="item.value">
{{ item.label }}
</el-radio-button>
</el-radio-group>
<el-button style="margin-left: 10px;" size="mini" @click="exportData">
<i class="el-icon-upload2"></i> 导出
</el-button>
</div>
<el-table height="calc(100vh - 210px)" v-loading="loading" :data="tableData"
style="width: 100%;border:1px solid #ccc;" element-loading-text="加载中..."
element-loading-spinner="el-icon-loading" element-loading-background="rgba(255, 255, 255, 0.8)">
<el-table-column prop="date" label="商品图片" width="180">
<template slot-scope="scope">
<el-image :src="scope.row.img || 'https://www.kaiyuancloud.cn/idfile?path=logo_ncmatch.png'"
style="width: 80px; height: 60px;border: 1px solid #ccc;border-radius: 6px;"
:preview-src-list="[scope.row.img || 'https://www.kaiyuancloud.cn/idfile?path=logo_ncmatch.png']"
fit="cover" :initial-index="0" preview-teleported :z-index="3000">
</el-image>
</template>
</el-table-column>
<el-table-column prop="product_name" show-overflow-tooltip label="商品名称" min-width="180">
</el-table-column>
<el-table-column prop="product_category" show-overflow-tooltip label="所属类别" min-width="180">
</el-table-column>
<el-table-column prop="company_name" show-overflow-tooltip label="所属企业" min-width="180">
</el-table-column>
<el-table-column prop="company_type" show-overflow-tooltip label="公司类别" min-width="180">
</el-table-column>
<el-table-column prop="address" show-overflow-tooltip label="商品概述" min-width="180">
</el-table-column>
<el-table-column prop="address" show-overflow-tooltip label="商品详情" min-width="180">
</el-table-column>
<el-table-column prop="contact_person" show-overflow-tooltip label="联系人" min-width="180">
</el-table-column>
<el-table-column prop="phone_number" show-overflow-tooltip label="联系电话" min-width="180">
</el-table-column>
<el-table-column prop="create_at" show-overflow-tooltip label="创建时间" min-width="180">
</el-table-column>
<el-table-column fixed="right" min-width="240" label="操作">
<template slot-scope="scope">
<el-button type="text" size="small" @click="openDetail(scope.row)">查看</el-button>
<el-button type="text" style="color: #409EFF;" size="small">修改</el-button>
<!-- <el-button type="text" style="color: #E6A23C;" size="small">导出</el-button> -->
<el-button type="text" style="color: #67C23A;" size="small">上架</el-button>
<el-button type="text" size="small">下架</el-button>
<el-button type="text" style="color: #F56C6C;" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination style="background-color: white;" @size-change="handleSizeChange"
@current-change="handleCurrentChange" :current-page.sync="current_page" :page-size="page_size"
layout="total, prev, pager, next" :total="total_count">
</el-pagination>
</div>
<commonDetail
:visible="showProductDetail"
@close="handleDetailClose"
@approve="handleApprove"
@reject="handleReject">
</commonDetail>
</div>
</template>
<script>
import { reqSearchByMangement, reqGetProductDetail } from '@/api/ncmatch';
import commonDetail from '@/views/customer/productMangement/commonDetail/index.vue';
import { mapState } from 'vuex';
export default {
name: 'commonBox',
components: { commonDetail },
props: {
role: {
type: Object,
default: () => { }
}
},
data() {
return {
searchDate: [],
searchData: {
radioType: "1",
start_date: null,
end_date: null,
keyword: "",
audit_status: null,
listing_status: null,
publish_type: "2",
manager_self: "",
url_link: window.location.href,
current_page: 1,
page_size: 8,
},
radioMap: {
'1': {
label: '用户需求',
publish_type: '2',
manager_self: null,
},
'2': {
label: '用户商品',
publish_type: '1',
manager_self: null,
},
'3': {
label: '平台需求',
publish_type: '2',
manager_self: 'single ',
},
'4': {
label: '平台商品',
publish_type: '1',
manager_self: 'single',
},
},
radioOptions: [
{
label: '用户需求',
value: '1'
},
{
label: '用户商品',
value: '2'
},
{
label: '平台需求',
value: '3'
},
{
label: '平台商品',
value: '4'
},
],
manager_self: null,
page_size: 10,
current_page: 1,
publish_type: 1,
tableData: [],
total_count: 0,
loading: false,
pickerOptions: {
shortcuts: [{
text: '最近一周',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近一个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近三个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
}
}]
},
}
},
created() {
this.getTableData();
},
methods: {
handleRadioChange(value) {
this.searchData.publish_type = this.radioMap[value].publish_type;
this.searchData.manager_self = this.radioMap[value].manager_self;
//
this.searchData.current_page = 1;
this.getTableData();
},
resetSearch() {
this.searchDate = [];
this.searchData.keyword = "";
this.searchData.current_page = 1;
this.getTableData();
},
exportData() {
this.$message.info('导出功能开发中...');
},
handleDetailClose() {
//
console.log('弹窗已关闭');
},
handleApprove() {
//
console.log('审核通过');
//
},
handleReject() {
//
console.log('审核不通过');
//
},
getTableData() {
//
if (this.searchDate && this.searchDate.length === 2) {
this.searchData.start_date = this.searchDate[0];
this.searchData.end_date = this.searchDate[1];
} else {
this.searchData.start_date = null;
this.searchData.end_date = null;
}
console.log("searchData", this.searchData);
this.loading = true;
reqSearchByMangement(this.searchData).then(res => {
console.log(res);
if (res.status) {
this.tableData = res.data.product_list;
this.total_count = res.data.total_count;
}
}).catch(error => {
console.error('获取数据失败:', error);
}).finally(() => {
this.loading = false;
})
},
//
handleSizeChange(val) {
console.log(`每页 ${val}`);
},
handleCurrentChange(val) {
this.getTableData();
console.log(`当前页: ${val}`);
},
openDetail(item) {
this.loading = true;
reqGetProductDetail({
id: item.id,
from: 'b'
}).then(async res => {
this.loading = false
if (res.status) {
await this.$store.commit('SETPRODUCTDETAIL', res.data);
await this.$store.commit('SHOWPRODUCTDETAIL', true);
} else {
this.$message.error(res.msg);
this.$set(this.loadingStates, item.id, false);
}
})
}
},
computed: {
showProductDetail: {
get() {
return this.$store.state.ncmatch.showProductDetail;
},
set(value) {
this.$store.commit('SHOWPRODUCTDETAIL', value);
}
}
}
}
</script>
<style scoped>
.search-box {
display: flex;
align-items: center;
justify-content: flex-start;
background-color: white;
padding: 10px;
li {
margin-right: 15px;
display: flex;
align-items: center;
justify-content: flex-start;
}
margin-bottom: 10px;
border-radius: 8px;
}
.table-box {
height: calc(100vh - 120px);
padding: 10px;
background-color: white;
}
::v-deep .radio-group {
.el-radio-button__inner {
border-radius: 4px;
margin-right: 8px;
border: 1px solid #dcdfe6;
background-color: #f5f7fa;
color: #606266;
&:hover {
background-color: #ecf5ff;
border-color: #409eff;
color: #409eff;
}
}
.el-radio-button__orig-radio:checked+.el-radio-button__inner {
background-color: #409eff;
border-color: #409eff;
color: #fff;
box-shadow: -1px 0 0 0 #409eff;
}
}
.search-label {
font-size: 14px;
font-weight: bold;
display: flex;
margin-right: 10px;
}
</style>

View File

@ -0,0 +1,92 @@
<template>
<el-dialog
:title="dialogTitle"
top="5vh"
:visible.sync="visible"
width="80%"
@open="scrollToTop"
@close="handleClose">
<ProductDetail></ProductDetail>
<span slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="success" @click="handleApprove">审核通过</el-button>
<el-button type="danger" @click="handleReject">审核不通过</el-button>
</span>
</el-dialog>
</template>
<script>
import ProductDetail from '@/views/homePage/ncmatch/proDetail/index.vue';
import { mapState } from 'vuex';
export default {
name: 'commonDetail',
components: { ProductDetail },
props: {
visible: {
type: Boolean,
default: false
}
},
computed: {
...mapState({
productDetailInfo: state => state.ncmatch.productDetail,
}),
dialogTitle() {
return this.productDetailInfo.publish_type === '1' ? '商品详情' : '需求详情';
}
},
methods: {
scrollToTop() {
this.$nextTick(() => {
const dialogBody = document.querySelector('.el-dialog__body');
if (dialogBody) {
dialogBody.scrollTop = 0;
}
});
},
handleClose() {
this.$store.commit('SHOWPRODUCTDETAIL', false);
this.$emit('close');
},
handleApprove() {
this.$confirm('确认审核通过吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// API
this.$emit('approve');
this.$message.success('审核通过成功');
this.handleClose();
}).catch(() => {
this.$message.info('已取消操作');
});
},
handleReject() {
this.$confirm('确认审核不通过吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// API
this.$emit('reject');
this.$message.success('审核不通过成功');
this.handleClose();
}).catch(() => {
this.$message.info('已取消操作');
});
}
}
}
</script>
<style scoped>
.dialog-footer {
text-align: right;
}
.dialog-footer .el-button {
margin-left: 10px;
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<div>
<router-view></router-view>
<el-dialog :title="productDetailInfo.publish_type === '1' ? '商品详情' : '需求详情'" top="5vh"
:visible.sync="showProductDetail" width="80%" @open="scrollToTop">
<ProductDetail></ProductDetail>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="closeProductDetail"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { mapGetters, mapState } from "vuex";
import ProductDetail from "@/views/homePage/ncmatch/proDetail/index.vue";
export default {
name: 'productMangement',
components: {
ProductDetail: ProductDetail
},
data() {
return {
}
},
methods: {
scrollToTop() {
window.scrollTo(0, 0);
},
closeProductDetail() {
this.$store.commit('SHOWPRODUCTDETAIL', false);
}
},
computed: {
...mapGetters(["sidebar", "avatar", "device"]),
...mapState({
productDetailInfo: state => state.ncmatch.productDetail,
showProductDetail: state => state.ncmatch.showProductDetail,
}),
},
}
</script>

View File

@ -0,0 +1,26 @@
<template>
<div>
<commonBox :role="role"></commonBox>
</div>
</template>
<script>
//| |
import commonBox from '@/views/customer/productMangement/commonBox/index.vue';
export default {
name: 'productList',
components: { commonBox },
data() {
return {
role:{
role_type:'customer',
type:'look'
}
}
},
created() {
},
}
</script>

View File

@ -1,21 +1,32 @@
<template> <template>
<div id="homeOut" class="homeOut"> <div id="homeOut" class="homeOut">
<TopBox id="topBox"></TopBox> <TopBox id="topBox"></TopBox>
<router-view></router-view> <div class="search-box">
<search></search>
</div>
<div style="width: 90%;max-width: 1600px;border:1px solid red;">
<router-view></router-view>
</div>
<el-dialog :title="productDetailInfo.publish_type==='1'? '商品详情' :'需求详情'" top="5vh" :visible.sync="showProductDetail" width="80%" @open="scrollToTop">
<ProductDetail></ProductDetail>
<span slot="footer" class="dialog-footer">
<!-- <el-button @click="closeProductDetail"> </el-button> -->
<el-button type="primary" @click="closeProductDetail"> </el-button>
</span>
</el-dialog>
<!-- footer--> <!-- footer-->
<div class="footer"> <div class="footer">
<div class="left-box" style="border-bottom: 1px solid #7A82A0"> <div class="left-box" style="border-bottom: 1px solid #7A82A0">
<div style="display: flex;flex-direction: column"> <div style="display: flex;flex-direction: column">
<img v-if="JSON.stringify(logoInfoNew)!=='{}'" style="width: 148px;height: 48px;" <img v-if="JSON.stringify(logoInfoNew) !== '{}'" style="width: 148px;height: 48px;"
:src="logoInfoNew.home.logoImg" alt="" :src="logoInfoNew.home.logoImg" alt="" class="img">
class="img">
<div class="content-main"> <div class="content-main">
<ul class="info"> <ul class="info">
<li>地址<span v-if="JSON.stringify(logoInfoNew)!=='{}'">{{ logoInfoNew.home.adress }}</span> <li>地址<span v-if="JSON.stringify(logoInfoNew) !== '{}'">{{ logoInfoNew.home.adress }}</span>
</li> </li>
<li v-if="JSON.stringify(logoInfoNew)!=='{}'"> 邮箱{{ logoInfoNew.home.email }}</li> <li v-if="JSON.stringify(logoInfoNew) !== '{}'"> 邮箱{{ logoInfoNew.home.email }}</li>
<li v-if="JSON.stringify(logoInfoNew)!=='{}'">电话: <span class="tel">{{ logoInfoNew.home.mobile }}</span> <li v-if="JSON.stringify(logoInfoNew) !== '{}'">电话: <span class="tel">{{ logoInfoNew.home.mobile }}</span>
</li> </li>
@ -28,7 +39,7 @@
</div> </div>
<ul class="bigUl"> <ul class="bigUl">
</ul> </ul>
<div v-if="JSON.stringify(logoInfoNew)!=='{}'&&logoInfoNew.home.bannerTitle!=='开元数智'" class="right-box"> <div v-if="JSON.stringify(logoInfoNew) !== '{}' && logoInfoNew.home.bannerTitle !== '开元数智'" class="right-box">
<div class="qr-box"> <div class="qr-box">
<div class="qr-code"> <div class="qr-code">
<img src="../img/kefu.jpg" style="padding: 0.08rem" alt=""> <img src="../img/kefu.jpg" style="padding: 0.08rem" alt="">
@ -44,14 +55,13 @@
</div> </div>
</div> </div>
<div style="display: flex;justify-content: center;align-items: center;width: 100%; "> <div style="display: flex;justify-content: center;align-items: center;width: 100%; ">
<span v-if="JSON.stringify(logoInfoNew)!=='{}'" <span v-if="JSON.stringify(logoInfoNew) !== '{}'"
style="margin:15px 0 ;width: 1400px;display:flex;justify-content:center;align-items:center;color: #7A82A0;"><span style="margin:15px 0 ;width: 1400px;display:flex;justify-content:center;align-items:center;color: #7A82A0;"><span
class="goStyle" class="goStyle" @click="goOut('https://beian.miit.gov.cn/#/Integrated/index')">
@click="goOut('https://beian.miit.gov.cn/#/Integrated/index')"> 京ICP备{{
京ICP备{{ logoInfoNew.home.license
logoInfoNew.home.license }}&nbsp;
}}&nbsp; </span> &nbsp;&nbsp;{{
</span> &nbsp;&nbsp;{{
logoInfoNew.home.footerTitle logoInfoNew.home.footerTitle
}}&nbsp;{{ }}&nbsp;{{
logoInfoNew.home.copyright logoInfoNew.home.copyright
@ -73,30 +83,37 @@
<script> <script>
import Vue from 'vue' import Vue from 'vue'
import TopBox from "@/views/homePage/components/topBox/index.vue"; import TopBox from "@/views/homePage/components/topBox/index.vue";
import {mapGetters, mapState} from "vuex"; import { mapGetters, mapState } from "vuex";
import search from "./search/index.vue";
import productDetail from "./proDetail/index.vue";
export default Vue.extend({ export default Vue.extend({
name: "indexLast", name: "indexLast",
components: {TopBox}, components: { TopBox, search, ProductDetail: productDetail },
data() { data() {
return { return {
currentBaseMenu: "hot", // currentBaseMenu: "hot", //
} }
}, },
computed: { computed: {
...mapGetters(["sidebar", "avatar", "device"]), ...mapGetters(["sidebar", "avatar", "device"]),
...mapState({ ...mapState({
isShowPanel: (state) => state.product.showHomeNav, isShowPanel: (state) => state.product.showHomeNav,
productDetailInfo: state => state.ncmatch.productDetail,
navIndex: (state) => state.product.navIndex, navIndex: (state) => state.product.navIndex,
gridObj: state => state.operationAnalysis.gridObj, gridObj: state => state.operationAnalysis.gridObj,
mybalance: state => state.user.mybalance, mybalance: state => state.user.mybalance,
logoutUrl: state => state.login.logoutUrl, logoutUrl: state => state.login.logoutUrl,
loginState: state => state.login.loginState, loginState: state => state.login.loginState,
logoInfoNew: state => state.product.logoInfoNew, logoInfoNew: state => state.product.logoInfoNew,
}), }),
showProductDetail: {
get() {
return this.$store.state.ncmatch.showProductDetail;
},
set(value) {
this.$store.commit('SHOWPRODUCTDETAIL', value);
}
},
showRegisterButton() { showRegisterButton() {
const orgType = window.sessionStorage.getItem('org_type'); const orgType = window.sessionStorage.getItem('org_type');
const userId = window.sessionStorage.getItem('userId'); const userId = window.sessionStorage.getItem('userId');
@ -109,6 +126,18 @@ export default Vue.extend({
}, },
methods: { methods: {
closeProductDetail() {
this.$store.commit('SHOWPRODUCTDETAIL', false)
},
scrollToTop() {
// 使nextTickDOM
this.$nextTick(() => {
const dialogBody = document.querySelector('.el-dialog__body');
if (dialogBody) {
dialogBody.scrollTop = 0;
}
});
},
goOut(url) { goOut(url) {
window.open(url) window.open(url)
}, },
@ -142,11 +171,25 @@ export default Vue.extend({
<style scoped lang="scss"> <style scoped lang="scss">
.search-box {
width: 100%;
margin-top: 55px;
border: 1px solid red;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
.homeOut { .homeOut {
//padding-top: 60px; //padding-top: 60px;
height: 100%; height: 100%;
overflow: auto !important; overflow: auto !important;
min-width: 1500px; min-width: 1500px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
} }

View File

@ -6,7 +6,9 @@ export default Vue.extend({
name: "mainPage", name: "mainPage",
components: { components: {
sendProduct: () => import('./sendProduct/index.vue'), sendProduct: () => import('./sendProduct/index.vue'),
productCard: () => import('./productCard/index.vue') productCard: () => import('./productCard/index.vue'),
search: () => import('../search/index.vue'),
menuAside: () => import('./menuAside/index.vue')
}, },
created() { created() {
this.init_product_list() this.init_product_list()
@ -190,47 +192,14 @@ export default Vue.extend({
<!-- </div> --> <!-- </div> -->
<!-- 中间搜索区域 -->
<div class="search-section" style="position: relative;">
<div class="search-bar">
<input v-model="searchKeyword" type="text" class="search-input" placeholder="搜你想搜...">
<button class="search-btn" @click="handleSearch">搜索</button>
</div>
<span
style="height: 44px;line-height: 44px;border-radius: 8px;border:1px solid #275AFF; color: #275AFF;font-size: 18px;font-weight: 500;position: absolute;right: -150px;top: 0px;padding: 0 20px;z-index: 10;display: flex;align-items: center;gap: 10px;">
<img style="width: 24px;height: 24px;" src="./img/robot.svg" alt="">
NC AI</span>
<!-- 热搜关键词 -->
<div class="hot-search">
<a v-for="keyword in hotSearchKeywords" :key="keyword" href="#" class="hot-keyword">
{{ keyword }}
</a>
</div>
</div>
</div> </div>
</header> </header>
<!-- 主内容区域 --> <!-- 主内容区域 -->
<main class="main-content"> <main class="main-content">
<div class="content-wrapper"> <div class="content-wrapper">
<!-- 左侧分类导航 -->
<aside class="category-sidebar">
<ul class="category-list">
<li class="category-item" style="color: #E02020;"><img src="./img/hot.svg" style="margin-right: 10px;"
alt=""> 热门推荐 / 活动促销</li>
<li v-for="category in categories" :key="category.name" class="category-item">
<span class="category-icon"> <img style="width: 24px;height: 24px;" :src="category.icon" alt=""> </span>
<span class="category-name">{{ category.name }}</span>
<span style="display: flex;margin-left: 0px;padding-left: 0px;">|</span>
<div class="menu-item">
<span v-for="(product, index) in category.product_list" :key="product">
{{ product }}{{ index < category.product_list.length - 1 ? ' / ' : '' }} </span>
</div>
</li>
</ul>
</aside>
<menuAside></menuAside>
<!-- 中间主内容 --> <!-- 中间主内容 -->
<section class="main-section"> <section class="main-section">
<!-- 顶部促销横幅 --> <!-- 顶部促销横幅 -->
@ -238,7 +207,7 @@ export default Vue.extend({
<div class="centerBox"> <div class="centerBox">
<span style="margin-top: 100px" class="title"> <span style="margin-top: 100px" class="title">
<span class="leftText"> <span class="leftText">
Ncmatch &nbsp;&nbsp; NCMatch &nbsp;&nbsp;
</span> </span>
<span class="rightText" style="margin-left: 10px;"> 您身边的AI管家</span> <span class="rightText" style="margin-left: 10px;"> 您身边的AI管家</span>
</span> </span>
@ -251,7 +220,7 @@ export default Vue.extend({
</section> </section>
<!-- 右侧用户信息栏 --> <!-- 右侧用户信息栏 -->
<aside class="user-sidebar" style="background-color: #f8fbfe;padding: 10px;padding-top: 20px;"> <aside class="user-sidebar" style="background-color: #f8fbfe;padding: 10px;padding-top: 20px;">
<span class="publish-goods" @click="$router.push('/ncmatchHome/supplyAndDemandSquare')"> 算力供需广场</span> <span class="publish-goods" @click="$router.push('/ncmatchHome/supplyAndDemandSquare')"> 算力供需广场</span>
<span class="publish-goods" @click="sendInfo('2')">发布需求</span> <span class="publish-goods" @click="sendInfo('2')">发布需求</span>
<span class="publish-goods" @click="sendInfo('1')">发布商品</span> <span class="publish-goods" @click="sendInfo('1')">发布商品</span>
@ -316,18 +285,11 @@ export default Vue.extend({
} }
} }
.menu-item {
flex: 1;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
/* 确保flex子项可以收缩 */
}
.productList { .productList {
width: 1600px; width: 100%;
max-width: 1600px;
margin: 0 auto; margin: 0 auto;
padding-bottom: 45px; padding-bottom: 45px;
} }
@ -513,66 +475,6 @@ export default Vue.extend({
} }
} }
.search-section {
flex: 1;
.search-bar {
display: flex;
align-items: center;
background: white;
border: 2px solid #3f68d8;
border-radius: 4px;
overflow: hidden;
.search-input {
flex: 1;
padding: 12px 15px;
border: none;
outline: none;
font-size: 14px;
}
.camera-icon {
padding: 0 10px;
color: #666;
cursor: pointer;
}
.search-btn {
background: #0864dd;
color: white;
border: none;
padding: 12px 20px;
cursor: pointer;
font-size: 14px;
&:hover {
background: #173ad4;
}
}
}
.hot-search {
margin-top: 8px;
display: flex;
gap: 15px;
flex-wrap: wrap;
.hot-keyword {
&:nth-child(-n+3) {
color: #e1251b;
}
color: #666;
text-decoration: none;
font-size: 12px;
&:hover {
color: #e1251b;
}
}
}
}
.signin-section { .signin-section {
.signin-btn { .signin-btn {
@ -598,6 +500,8 @@ export default Vue.extend({
// //
.main-content { .main-content {
padding: 16px; padding: 16px;
width: 100%;
max-width: 1600px; max-width: 1600px;
margin: 20px auto; margin: 20px auto;
margin-top: 10px; margin-top: 10px;
@ -614,53 +518,6 @@ export default Vue.extend({
} }
} }
//
.category-sidebar {
background-color: #f8fbfe;
height: 100%;
border-radius: 10px;
// padding: 15px;
padding: 5px;
.category-list {
list-style: none;
padding: 0;
margin: 0;
.category-item {
display: flex;
margin-bottom: 10px;
align-items: center;
gap: 10px;
padding: 4px 0;
cursor: pointer;
transition: all 0.3s;
padding: 0 10px;
&:hover {
color: #2c96fc;
background: #c3daee !important;
}
&:last-child {
border-bottom: none;
}
.category-icon {
width: 20px;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
.category-name {
font-size: 14px;
}
}
}
}
// //
.main-section { .main-section {

View File

@ -0,0 +1,39 @@
export function buildDynamicStructure(data, parentId = "None", currentLevel = 1) {
// 1. 找出当前层级的节点
const currentNodes = data.filter(item => item.parentid === parentId);
if (currentNodes.length === 0) return [];
// 2. 处理每个节点
return currentNodes.map(node => {
const resultNode = {};
// 设置层级名称字段
if (currentLevel === 1) {
resultNode.first_level_name = node.name;
resultNode.icon = node.icon || require('../img/cloud.png');
} else if (currentLevel === 2) {
resultNode.second_level_name = node.name;
} else if (currentLevel === 3) {
resultNode.third_level_name = node.name;
}
// 3. 递归处理子节点
const children = buildDynamicStructure(data, node.id, currentLevel + 1);
// 4. 根据当前层级设置子节点字段
if (children.length > 0) {
if (currentLevel === 1) {
resultNode.secondaryClassification = children;
} else if (currentLevel === 2) {
resultNode.thirdClassification = children;
} else if (currentLevel === 3) {
// 第四级特殊处理为product_list
resultNode.product_list = children.map(child => ({
first_level_name: child.third_level_name || child.name
}));
}
}
return resultNode;
});
}

View File

@ -0,0 +1,502 @@
<template>
<!-- 左侧分类导航 -->
<aside class="category-sidebar">
<ul class="category-list">
<li class="category-item" style="color: #E02020;"><img src="../img/hot.svg" style="margin-right: 10px;"
alt=""> 热门推荐 / 活动促销</li>
<li v-for="category in categories" :key="category.first_level_name" class="category-item"
@mouseenter="showProductList(category)" @mouseleave="hideProductList">
<span class="category-icon"> <img style="width: 24px;height: 24px;" :src="category.icon" alt=""> </span>
<span class="category-name">{{ category.first_level_name }}</span>
<span style="display: flex;margin-left: 0px;padding-left: 0px;">|</span>
<div class="menu-item">
<span v-for="(secondary, index) in category.secondaryClassification"
:key="secondary.second_level_name">
{{ secondary.second_level_name }}{{ index < category.secondaryClassification.length - 1 ? ' / '
: '' }} </span>
</div>
</li>
</ul>
<transition name="slide-fade">
<!-- v-if="currentCategory" -->
<div class="rightBox" v-if="currentCategory" @mouseenter="keepProductList" @mouseleave="hideProductList">
<div class="rightBox-content">
<!-- 二级菜单标题 -->
<div class="secondary-menu">
<div v-for="secondary in currentCategory.secondaryClassification"
:key="secondary.second_level_name" class="secondary-item"
:class="{ active: selectedSecondary === secondary }"
@mouseenter="selectSecondary(secondary)">
{{ secondary.second_level_name }}
</div>
</div>
<!-- 三级和四级菜单内容 -->
<div class="menu-content">
<!-- 如果有选中的二级菜单且有三级菜单显示京东风格的分类区域 -->
<div v-if="selectedSecondary && selectedSecondary.thirdClassification && selectedSecondary.thirdClassification.length > 0"
class="jd-style-menu">
<div v-for="third in selectedSecondary.thirdClassification" :key="third.third_level_name"
class="category-section">
<!-- 只有当有四级菜单时才显示三级菜单标题 -->
<div v-if="third.product_list && third.product_list.length > 0" class="section-header">
<span class="section-title">{{ third.third_level_name }}</span>
<span class="section-arrow">></span>
</div>
<div class="section-content">
<!-- 如果有四级菜单product_list直接显示所有四级菜单项 -->
<div v-if="third.product_list && third.product_list.length > 0"
class="product-grid">
<div v-for="(product, index) in third.product_list"
:key="product.first_level_name" class="product-tag">
{{ product.first_level_name }}
</div>
</div>
<!-- 如果没有四级菜单将三级菜单项视为四级菜单项显示 -->
<div v-else class="product-grid">
<div class="product-tag ">
{{ third.third_level_name }}
</div>
</div>
</div>
</div>
</div>
<!-- 如果没有三级菜单显示二级菜单项 -->
<div v-else-if="selectedSecondary" class="jd-style-menu">
<div class="category-section">
<div class="section-header">
<span class="section-title">{{ selectedSecondary.second_level_name }}</span>
<span class="section-arrow">></span>
</div>
<div class="section-content">
<div class="product-grid">
<div class="product-tag hot-tag">
{{ selectedSecondary.second_level_name }}
</div>
</div>
</div>
</div>
</div>
<!-- 默认显示所有二级菜单项 -->
<div v-else class="jd-style-menu">
<div v-for="secondary in currentCategory.secondaryClassification"
:key="secondary.second_level_name" class="category-section">
<div class="section-header">
<span class="section-title">{{ secondary.second_level_name }}</span>
<span class="section-arrow">></span>
</div>
<div class="section-content">
<div class="product-grid">
<div class="product-tag ">
{{ secondary.second_level_name }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</transition>
</aside>
</template>
<script>
import { reqNcMatchMenu } from '@/api/ncmatch';
import { buildDynamicStructure } from './buildNcmatchTree';
export default {
name: 'menuAside',
data() {
return {
currentCategory: null,
selectedSecondary: null,
hideTimer: null,
categories: [
{
first_level_name: '云', icon: require('../img/cloud.png'), secondaryClassification: [
{
second_level_name: '百度云',
thirdClassification: [
{
third_level_name: '计算',
product_list: [{ first_level_name: 'ECS1' }, { first_level_name: 'ECS2' }, { first_level_name: 'ECS3' }]
},
{
third_level_name: '存储',
product_list: [{ first_level_name: 'BOS1' }, { first_level_name: 'BOS2' }]
}
]
},
{
second_level_name: '阿里云',
thirdClassification: [
{
third_level_name: '计算',
product_list: [{ first_level_name: 'ECS-A' }, { first_level_name: 'ECS-B' }]
}
]
}
]
},
{
first_level_name: '国产算力', icon: require('../img/cloud.png'), secondaryClassification: [
{
second_level_name: '昇腾910B',
thirdClassification: [
{
third_level_name: '昇腾910B1',
},
{
third_level_name: '昇腾910B2',
}
]
},
{
second_level_name: '昆仑芯',
thirdClassification: [
{
third_level_name: '昆仑芯A',
},
{
third_level_name: '昆仑芯B',
}
]
}
]
}
],
}
},
created() {
this.getCategories();
},
methods: {
getCategories() {
reqNcMatchMenu({ url_link: window.location.href }).then(res => {
if (res.status) {
this.categories = buildDynamicStructure(res.data)
// this.categories = res.data.map(item => {
// return {
// name: item.name,
// icon: item.icon,
// product_list: item.product_list
// }
// });
}
})
},
showProductList(category) {
//
if (this.hideTimer) {
clearTimeout(this.hideTimer);
this.hideTimer = null;
}
this.currentCategory = category;
//
if (category.secondaryClassification && category.secondaryClassification.length > 0) {
this.selectedSecondary = category.secondaryClassification[0];
} else {
this.selectedSecondary = null;
}
},
selectSecondary(secondary) {
this.selectedSecondary = secondary;
},
keepProductList() {
//
if (this.hideTimer) {
clearTimeout(this.hideTimer);
this.hideTimer = null;
}
},
hideProductList() {
// rightBox
this.hideTimer = setTimeout(() => {
this.currentCategory = null;
this.selectedSecondary = null;
this.hideTimer = null;
}, 200);
}
}
}
</script>
<style scoped lang="scss">
.menu-item {
flex: 1;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
/* 确保flex子项可以收缩 */
}
//
.category-sidebar {
background-color: #f8fbfe;
height: 100%;
border-radius: 10px;
// padding: 15px;
padding: 5px;
.category-list {
list-style: none;
padding: 0;
margin: 0;
.category-item {
display: flex;
margin-bottom: 10px;
align-items: center;
gap: 10px;
padding: 4px 0;
cursor: pointer;
transition: all 0.3s;
padding: 0 10px;
&:hover {
color: #2c96fc !important;
background: #c3daee !important;
}
&:last-child {
border-bottom: none;
}
.category-icon {
width: 20px;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
.category-name {
font-size: 14px;
}
}
}
}
@media (max-width: 768px) {
.main-content .content-wrapper {
grid-template-columns: 1fr;
}
.category-sidebar,
.user-sidebar {
display: none;
}
.header .header-content {
flex-direction: column;
gap: 10px;
}
.search-section {
width: 100%;
}
}
.rightBox {
position: absolute;
left: 100%;
top: 0;
width: 900px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
padding: 25px;
min-height: 400px;
margin-left: 10px;
/* 添加一个透明的连接区域,防止鼠标移动时意外消失 */
&::before {
content: '';
position: absolute;
left: -10px;
top: 0;
width: 10px;
height: 100%;
background: transparent;
}
.rightBox-content {
.secondary-menu {
line-height: 1.5;
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 20px;
.secondary-item {
padding: 4px 8px;
background: #f8f9fa;
border-radius: 4px;
cursor: pointer;
display: flex;
transition: all 0.3s ease;
font-size: 12px;
color: #333;
font-weight: 500;
&:hover,
&.active {
background-color: #ffebf1;
color: #ff0f23;
}
}
}
.menu-content {
min-height: 200px;
.jd-style-menu {
width: 100%;
display: flex;
flex-direction: column;
gap: 20px;
border: 1px solid red;
.category-section {
display: flex;
justify-content: flex-start;
align-items: center;
border: 1px solid blue;
.section-header {
display: flex;
align-items: center;
.section-title {
font-size: 12px;
font-weight: 600;
color: #333;
border: 1px solid red;
;
margin-right: 8px;
}
.section-arrow {
color: #999;
font-size: 12px;
}
}
.section-content {
line-height: 1.5;
.product-grid {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
.product-tag {
border: 1px solid red;
height: fit-content !important;
background: #f8f9fa;
font-size: 12px;
color: #666;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
position: relative;
&:hover {
color: #e1251b;
}
}
}
}
}
}
.hot-tag {
color: #e1251b !important;
border-color: #e1251b !important;
background: #fff5f5 !important;
font-weight: 500 !important;
position: relative;
&::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 2px;
background: #e1251b;
border-radius: 0 0 4px 4px;
}
}
.product-list {
flex: 1;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
align-content: start;
justify-content: center;
.product-item {
width: fit-content;
border-radius: 6px;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
&:hover {
* {
color: #2c96fc;
}
}
.product-name {
font-size: 14px;
color: #333;
font-weight: 500;
}
}
}
}
}
}
.category-sidebar {
position: relative;
}
/* 过渡动画样式 */
.slide-fade-enter-active {
transition: all 0.3s ease;
}
.slide-fade-leave-active {
transition: all 0.3s ease;
}
.slide-fade-enter {
// transform: translateX(-20px);
opacity: 0;
}
.slide-fade-leave-to {
// transform: translateX(-20px);
opacity: 0;
}
.slide-fade-enter-to,
.slide-fade-leave {
// transform: translateX(0);
opacity: 1;
}
</style>

View File

@ -1,91 +1,109 @@
<template> <template>
<!-- 产品列表 --> <!-- 产品列表 -->
<div> <div>
<ul v-if="type === 'homePage'" class="productListContent" style="padding: 16px;"> <ul v-if="type === 'homePage'" class="productListContent" style="padding-top: 16px;">
<li class="product-item" v-for="item in productList" :key="item.id"> <li class="product-item" v-for="item in productList" :key="item.id">
<div class="product-image"> <div v-if="item.img!=null" class="product-image">
<img :src="item.img" style="min-width: 278px;min-height: 185px;" :alt="item.name"> <img :src="item.img" style="min-width: 278px;min-height: 185px;" :alt="item.name">
</div>
<div class="product-info">
<h3 class="product-name">{{ item.product_name }}</h3>
<div class="product-specs">
<div class="spec-item" v-if="type === 'homePage'">
<span class="spec-label">企业名称:</span>
<span class="spec-value">{{ item.company_name }}</span>
</div>
<div class="spec-item">
<span class="spec-label">CPU:</span>
<span class="spec-value">{{ item.cpu }}</span>
</div>
<div class="spec-item">
<span class="spec-label">内存:</span>
<span class="spec-value">{{ item.memory }}</span>
</div>
<div class="spec-item">
<span class="spec-label">GPU:</span>
<span class="spec-value">{{ item.gpu }}</span>
</div>
<div class="spec-item">
<span class="spec-label">系统盘:</span>
<span class="spec-value">{{ item.sys_disk }}</span>
</div>
<div class="spec-item">
<span class="spec-label">数据盘:</span>
<span class="spec-value">{{ item.data_disk }}</span>
</div>
<div class="spec-item">
<span class="spec-label">网卡:</span>
<span class="spec-value">{{ item.net_card }}</span>
</div>
</div> </div>
<div class="product-price"> <div class="product-info">
<span class="price">¥{{ item.discount_price }}</span> <h3 class="product-name">{{ item.product_name }}</h3>
<span class="price-unit">{{ item.unit }} {{ item.short_term==='1' ? '(可短租)' : '' }} </span> <div class="product-specs">
</div> <div class="spec-item" v-if="type === 'homePage'">
<button class="consult-btn" @click="openTalk">立即咨询</button> <span class="spec-label">企业名称:</span>
</div> <span class="spec-value">{{ item.company_name }}</span>
</li> </div>
</ul> <div v-if="item.cart_flag==='1'" class="spec-item">
<ul v-else-if="type === 'supplyAndDemandSquare'" class="productListContent" style="padding: 16px;"> <span class="spec-label">CPU:</span>
<li class="product-item" v-for="item in productList" :key="item.id"> <span class="spec-value">{{ item.cpu }}</span>
<div class="product-image"> </div>
<img :src="item.img" style="min-width: 278px;min-height: 185px;" :alt="item.name"> <div v-if="item.cart_flag==='1'" class="spec-item">
</div> <span class="spec-label">内存:</span>
<div class="product-info"> <span class="spec-value">{{ item.memory }}</span>
<h3 class="product-name">{{ item.product_name }}</h3> </div>
<div class="product-specs"> <div v-if="item.cart_flag==='1'" class="spec-item">
<div class="spec-item" > <span class="spec-label">GPU:</span>
<span class="spec-label">企业名称:</span> <span class="spec-value">{{ item.gpu }}</span>
<span class="spec-value">{{ item.company_name }}</span> </div>
<div v-if="item.cart_flag==='1'" class="spec-item">
<span class="spec-label">系统盘:</span>
<span class="spec-value">{{ item.sys_disk }}</span>
</div>
<div v-if="item.cart_flag==='1'" class="spec-item">
<span class="spec-label">数据盘:</span>
<span class="spec-value">{{ item.data_disk }}</span>
</div>
<div v-if="item.cart_flag==='1'" class="spec-item">
<span class="spec-label">网卡:</span>
<span class="spec-value">{{ item.net_card }}</span>
</div>
<div v-if="item.cart_flag!=='1'" class="spec-item">
<span cl ass="spec-label">商品描述:</span>
<span class="spec-value showText">{{ item.requirement_summary }}</span>
</div>
<div v-if="item.cart_flag!=='1'" class="spec-item">
<span class="spec-label">相关参数:</span>
<span class="spec-value showText">{{ item.related_parameters }}</span>
</div>
<div v-if="item.cart_flag!=='1'" class="spec-item">
<span class="spec-label">应用场景:</span>
<span class="spec-value showText">{{ item.application_scenario }}</span>
</div>
</div> </div>
<div class="spec-item"> <div class="product-price">
<span class="spec-label">所属类别:</span> <span class="price">¥{{ item.discount_price }}</span>
<span class="spec-value">{{ item.company_type }}</span> <span class="price-unit">{{ item.unit }} {{ item.short_term === '1' ? '(可短租)' : '' }} </span>
</div>
<div class="spec-item">
<span class="spec-label">更新日期:</span>
<span class="spec-value">{{ item.update_time }}</span>
</div>
<div class="spec-item">
<span class="spec-label">{{ publish_type === '1' ? '商品描述' : '需求描述' }}:</span>
<span class="spec-value showText">{{ item.requirement_summary }}</span>
</div> </div>
<div style="display: flex;justify-content: space-between;align-items: center;">
<el-button :disabled="loadingStates[item.id]" class="consult-btn" @click="openTalk">立即咨询</el-button> <span @click="openDetail(item)"
class="detail-btn">详情 <i v-if="loadingStates[item.id]" class="el-icon-loading"></i> <span v-else>>></span> </span>
</div>
<div style="display: flex;justify-content: space-between;align-items: center;">
<button class="consult-btn" @click="openTalk">立即咨询</button> <span class="detail-btn">详情>></span>
</div>
</div>
</li>
</ul>
<Talk></Talk>
</div> </div>
</div>
</li>
</ul>
<ul v-else-if="type === 'supplyAndDemandSquare'" class="productListContent" style="padding: 16px;">
<li class="product-item" v-for="item in productList" :key="item.id">
<div v-if="item.img!=null" class="product-image">
<img :src="item.img" style="min-width: 278px;min-height: 185px;" :alt="item.name">
</div>
<div class="product-info">
<h3 class="product-name">{{ item.product_name }}</h3>
<div class="product-specs">
<div class="spec-item">
<span class="spec-label">企业名称:</span>
<span class="spec-value">{{ item.company_name }}</span>
</div>
<div class="spec-item">
<span class="spec-label">所属类别:</span>
<span class="spec-value">{{ item.company_type }}</span>
</div>
<div class="spec-item">
<span class="spec-label">更新日期:</span>
<span class="spec-value">{{ item.update_time }}</span>
</div>
<div class="spec-item">
<span class="spec-label">{{ publish_type === '1' ? '商品描述' : '需求描述' }}:</span>
<span class="spec-value showText">{{ item.requirement_summary }}</span>
</div>
</div>
<div style="display: flex;justify-content: space-between;align-items: center;">
<el-button :disabled="loadingStates[item.id]" class="consult-btn" @click="openTalk">立即咨询</el-button> <span @click="openDetail(item)"
class="detail-btn">详情 <i v-if="loadingStates[item.id]" class="el-icon-loading"></i> <span v-else>>></span> </span>
</div>
</div>
</li>
</ul>
<Talk></Talk>
</div>
</template> </template>
<script> <script>
import Talk from '@/views/homePage/dialog/talk/index.vue' import Talk from '@/views/homePage/dialog/talk/index.vue'
import { reqGetProductDetail } from '@/api/ncmatch'
export default { export default {
name: 'productCard', name: 'productCard',
components: { components: {
@ -105,15 +123,39 @@ export default {
default: '1' default: '1'
} }
}, },
data(){
return{
loadingStates: {}
}
},
methods: { methods: {
openTalk() { openTalk() {
this.$store.commit('setShowTalk', true); this.$store.commit('setShowTalk', true);
}, },
} async openDetail(item) {
this.$set(this.loadingStates, item.id, true);
reqGetProductDetail({
id: item.id,
from:'f'
}).then(res => {
if(res.status){
this.$store.commit('SETPRODUCTDETAIL', res.data);
this.$store.commit('SHOWPRODUCTDETAIL', true);
this.$set(this.loadingStates, item.id, false);
}else{
this.$message.error(res.msg);
this.$set(this.loadingStates, item.id, false);
}
})
}
}
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.showText{ .showText {
// //
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 4; -webkit-line-clamp: 4;
@ -123,11 +165,15 @@ export default {
white-space: normal; white-space: normal;
word-break: break-all; word-break: break-all;
} }
.productListContent { .productListContent {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
gap: 20px; gap: 20px;
padding: 16px; // padding: 16px;
padding-top: 16px;
width: 100%;
max-width: 1600px; max-width: 1600px;
margin: 0 auto; margin: 0 auto;
background-color: white; background-color: white;
@ -238,16 +284,19 @@ export default {
transition: all 0.3s ease; transition: all 0.3s ease;
&:hover { &:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(39, 90, 255, 0.3); box-shadow: 0 4px 12px rgba(39, 90, 255, 0.3);
} }
} }
.detail-btn{
.detail-btn {
display: flex;
align-items: center;
justify-content: flex-start;
font-size: 14px; font-size: 14px;
color: #275AFF; color: #275AFF;
cursor: pointer; cursor: pointer;
min-width: 50px; min-width: 50px;
margin-left: 25px; margin-left: 25px;
} }
} }
} }

View File

@ -71,8 +71,15 @@
<el-input v-model="form.email" placeholder="请输入邮箱地址"></el-input> <el-input v-model="form.email" placeholder="请输入邮箱地址"></el-input>
</el-form-item> </el-form-item>
</div> </div>
<div class="form-row">
<div class="form-row" v-if="current_product_category.cart_flag == '1'"> <el-form-item label="GPU支持" prop="cart_flag" required class="form-item-half">
<el-radio-group v-model="form.cart_flag">
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
</el-radio-group>
</el-form-item>
</div>
<div class="form-row" v-if="form.cart_flag == '1'">
<el-form-item label="CPU" prop="cpu" class="form-item-half"> <el-form-item label="CPU" prop="cpu" class="form-item-half">
<el-input v-model="form.cpu" placeholder="请输入CPU规格"></el-input> <el-input v-model="form.cpu" placeholder="请输入CPU规格"></el-input>
</el-form-item> </el-form-item>
@ -82,7 +89,7 @@
</el-form-item> </el-form-item>
</div> </div>
<div class="form-row" v-if="current_product_category.cart_flag == '1'"> <div class="form-row" v-if="form.cart_flag == '1'">
<el-form-item label="GPU" prop="gpu" class="form-item-half"> <el-form-item label="GPU" prop="gpu" class="form-item-half">
<el-input v-model="form.gpu" placeholder="请输入GPU规格"></el-input> <el-input v-model="form.gpu" placeholder="请输入GPU规格"></el-input>
</el-form-item> </el-form-item>
@ -92,7 +99,7 @@
</el-form-item> </el-form-item>
</div> </div>
<div class="form-row" v-if="current_product_category.cart_flag == '1'"> <div class="form-row" v-if="form.cart_flag == '1'">
<el-form-item label="数据盘" prop="data_disk" class="form-item-half"> <el-form-item label="数据盘" prop="data_disk" class="form-item-half">
<el-input v-model="form.data_disk" placeholder="请输入数据盘规格"></el-input> <el-input v-model="form.data_disk" placeholder="请输入数据盘规格"></el-input>
</el-form-item> </el-form-item>
@ -239,6 +246,7 @@ export default {
unit: "",// unit: "",//
short_term: "",// short_term: "",//
discount:null,// discount:null,//
cart_flag: '1',//GPU
}, },
rules: { rules: {
product_name: [ product_name: [

View File

@ -0,0 +1,521 @@
<template>
<div class="enterprise-demand-detail">
<div style="display: flex;justify-content: space-between;align-items: center;">
<h1 class="demand-title" style="margin: 0 0;">
{{ productDetailInfo.product_name }}
</h1>
<button class="consult-btn" @click="openTalk">立即咨询</button>
</div>
<!-- 标题区域 -->
<ul class="title-section">
<li class="info-item" style="margin:5px 0;margin-top: 15px;">
<span class="mylabel">企业名称</span>
<span class="value">{{ productDetailInfo.company_name }}</span>
</li>
<li class="info-item" style="margin:5px 0;">
<span class="mylabel">所属类别</span>
<span class="value">{{ productDetailInfo.product_category }}</span>
</li>
<li class="info-item" style="margin:5px 0;">
<span class="mylabel">企业类别</span>
<span class="value">{{ productDetailInfo.company_type }}</span>
</li>
</ul>
<!-- 联系信息区域 -->
<ul class="infoUl">
<li class="contact-item">
<span class="mylabel">联系人</span>
<span class="value">{{ productDetailInfo.contact_person }} {{ productDetailInfo.phone_number }}</span>
</li>
<li class="contact-item">
<span class="mylabel">邮箱</span>
<span class="value">{{ productDetailInfo.email }}</span>
</li>
<li class="contact-item">
<span class="mylabel">职务</span>
<span class="value">{{ productDetailInfo.job_title ? productDetailInfo.job_title : '--' }}</span>
</li>
<li class="contact-item">
<span class="mylabel">发布日期</span>
<span class="value">{{ productDetailInfo.create_at ? productDetailInfo.create_at : '暂未发布' }}</span>
</li>
</ul>
<div v-if="productDetailInfo.cart_flag==='1'" style="position: relative;width: 100%;padding-left: 16px;" class="mysection">
<div class="section-title">配置数据</div>
<div class="section-content">
</div>
</div>
<ul v-if="productDetailInfo.cart_flag==='1'" class="adUl">
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/ssc2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">CPU</span>
<span class="subDescription">{{ productDetailInfo.cpu }}</span>
</div>
</li>
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/cpu2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">内存</span>
<span class="subDescription">{{ productDetailInfo.memory }}</span>
</div>
</li>
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/sdcard2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">GPU</span>
<span class="subDescription">{{ productDetailInfo.gpu }}</span>
</div>
</li>
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/ssdr2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">系统盘</span>
<span class="subDescription">{{ productDetailInfo.data_disk }}</span>
</div>
</li>
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/servermini2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">数据盘</span>
<span class="subDescription">{{ productDetailInfo.sys_disk }}</span>
</div>
</li>
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/serverpath2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">网卡</span>
<span class="subDescription">{{ productDetailInfo.net_card }}</span>
</div>
</li>
</ul>
<!-- 配置数据区域 -->
<!-- 需求描述区域 -->
<div class="demand-section">
<div class="demand-content">
<div class="section-title">需求描述</div>
{{ productDetailInfo.requirement_summary ? productDetailInfo.requirement_summary : '--' }}
</div>
</div>
<!-- 相关参数区域 -->
<div class="params-section">
<div class="params-content">
<div class="section-title">相关参数</div>
{{ productDetailInfo.related_parameters ? productDetailInfo.related_parameters : '--' }}
</div>
</div>
<!-- 预期目标区域 -->
<div class="goals-section">
<div class="goals-content">
<div class="section-title">应用场景</div>
{{ productDetailInfo.application_scenario ? productDetailInfo.application_scenario : '--' }}
</div>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: 'proDetail',
data() {
return {
}
},
computed: {
...mapState({
productDetailInfo: state => state.ncmatch.productDetail,
showProductDetail: state => state.ncmatch.showProductDetail
})
},
methods: {
openTalk() {
this.$store.commit('setShowTalk', true);
}
}
}
</script>
<style scoped lang="scss">
.consult-btn {
width: fit-content;
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(39, 90, 255, 0.3);
}
}
.mylabel {
font-weight: 500;
color: #252424;
min-width: 80px;
}
.infoUl {
border-top: 1px solid #e5e5e5;
padding: 30px 0;
display: flex;
justify-content: space-between;
align-items: center;
li {
margin-bottom: 10px;
}
}
.adUl {
margin-bottom: 30px;
background: url("../../../homePage/detail/img/liBg.png");
background-position: top;
background-size: cover;
width: 100%;
min-width: 800px;
display: flex !important;
flex-wrap: wrap !important;
justify-content: space-between !important;
align-items: center !important;
li {
margin: 15px 0;
width: 49%;
display: flex !important;
justify-content: flex-start;
align-items: center;
.leftBox {
width: 50px;
height: 50px;
padding: 10px;
min-width: 50px;
min-height: 50px;
img {
width: 100%;
height: 100%;
}
}
.rightBox {
padding-left: 25px;
display: flex !important;
flex-direction: column !important;
.subTitle {
font-size: 16px;
color: #333;
}
.subDescription {
margin-top: 10px;
font-size: 14px;
color: #999;
}
}
}
}
.enterprise-demand-detail {
margin: 0 auto;
padding: 16px;
padding-top: 0;
background: #fff;
font-family: 'Microsoft YaHei', sans-serif;
}
//
.header-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
.header-left {
.demand-icon {
display: flex;
align-items: center;
gap: 10px;
i {
background: #67c23a;
color: white;
padding: 8px;
border-radius: 4px;
font-size: 16px;
}
span {
font-size: 16px;
font-weight: 500;
color: #333;
}
}
}
.header-right {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
.consult-btn {
background: #67c23a;
color: white;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background 0.3s;
&:hover {
background: #5daf34;
}
}
.demand-number {
font-size: 12px;
color: #666;
}
}
}
//
.title-section {
margin-bottom: 30px;
.demand-title {
font-size: 24px;
font-weight: bold;
color: #333;
margin: 0;
}
}
//
.company-info {
margin-bottom: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
.info-item {
display: flex;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.label {
font-weight: 500;
color: #666;
min-width: 80px;
}
.value {
color: #333;
}
}
}
//
.contact-info {
margin-bottom: 30px;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
.contact-item {
display: flex;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.label {
font-weight: 500;
color: #666;
min-width: 80px;
}
.value {
color: #333;
}
}
}
//
.collaboration-section {
margin-bottom: 30px;
.collab-title {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 15px;
}
.collab-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
.collab-btn {
padding: 8px 16px;
background: #e8f5e8;
color: #67c23a;
border: 1px solid #67c23a;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: #67c23a;
color: white;
}
}
}
}
//
.mysection,
.config-section,
.demand-section,
.params-section,
.goals-section {
margin-bottom: 30px;
.section-title {
margin-bottom: 10px;
font-size: 14px;
color: #333;
font-weight: bold !important;
position: relative;
&::before {
content: '';
position: absolute;
top: 6px;
left: -5px;
display: block;
width: 2px;
height: 12px;
background: #666;
}
}
}
//
.config-section {
.config-content {
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
.config-item {
display: flex;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.config-label {
font-weight: 500;
color: #666;
min-width: 80px;
}
.config-value {
color: #333;
}
}
}
}
//
.demand-section,
.params-section,
.goals-section {
.demand-content,
.params-content,
.goals-content {
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
line-height: 1.8;
color: #333;
text-align: justify;
}
}
//
@media (max-width: 768px) {
.enterprise-demand-detail {
padding: 15px;
}
.header-section {
flex-direction: column;
gap: 15px;
text-align: center;
}
.collab-buttons {
justify-content: center;
}
.info-item,
.contact-item,
.config-item {
flex-direction: column;
gap: 5px;
.label {
min-width: auto;
}
}
}
</style>

View File

@ -0,0 +1,161 @@
<template>
<div
style="display: flex;align-items: center;justify-content: center;border:1px solid red;width: 100%;max-width: 1400px;">
<img style="width: 180px;height: 60px;padding-right: 20px;" src="https://www.kaiyuancloud.cn/idfile?path=logo_ncmatch.png" alt="">
<div
style="min-width:800px;border-bottom: 1px solid #E5E5E5;display: flex;align-items: center;justify-content: space-between;">
<!-- 中间搜索区域 -->
<div class="search-section" style="position: relative;margin: 0 20px;">
<div class="search-bar">
<input v-model="searchKeyword" type="text" class="search-input" placeholder="搜你想搜...">
<button class="search-btn" @click="handleSearch">搜索</button>
</div>
<!-- 热搜关键词 -->
<div class="hot-search">
<a v-for="keyword in hotSearchKeywords" :key="keyword" href="#" class="hot-keyword">
{{ keyword }}
</a>
</div>
</div>
<!-- position: absolute;right: -150px;top: 0px; -->
<span
style="height: 44px;line-height: 44px;border-radius: 8px;border:1px solid #275AFF; color: #275AFF;font-size: 18px;font-weight: 500;padding: 0 20px;z-index: 10;display: flex;align-items: center;gap: 10px;">
<img style="width: 24px;height: 24px;" src="../mainPage/img/robot.svg" alt="">
NC AI</span>
</div>
</div>
</template>
<script>
export default {
name: 'search',
data() {
return {
hotSearchKeywords: [
'昆仑芯-P800',
'天垓-150',
'4090',
'云服务器-GPU',
'人脸识别',
'SDWAN',
'互联网专线',
'DCI',
'AI专线',
'对象存储',
'智慧医疗',
'智慧客服',
'A800',
'A100',
'910B'
],
hotMenuList: [
{
id: "hot",
name: "热门推荐",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png")
},
{
id: "hlzx",
name: "互联网专线",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
},
{
id: "SDWAN",
name: "SDWAN",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
},
{
id: "DCI",
name: "DCI",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
},
{
id: "AI",
name: "AI专线",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
}
],
}
}
}
</script>
<style scoped lang="scss">
.search-section {
border: 1px solid red;
flex: 1;
min-width: 850px;
.search-bar {
display: flex;
align-items: center;
background: white;
border: 2px solid #3f68d8;
border-radius: 4px;
overflow: hidden;
.search-input {
flex: 1;
padding: 12px 15px;
border: none;
outline: none;
font-size: 14px;
}
.camera-icon {
padding: 0 10px;
color: #666;
cursor: pointer;
}
.search-btn {
background: #0864dd;
color: white;
border: none;
padding: 12px 20px;
cursor: pointer;
font-size: 14px;
&:hover {
background: #173ad4;
}
}
}
.hot-search {
margin-top: 8px;
display: flex;
gap: 15px;
flex-wrap: wrap;
.hot-keyword {
&:nth-child(-n+3) {
color: #e1251b;
}
color: #666;
text-decoration: none;
font-size: 12px;
&:hover {
color: #e1251b;
}
}
}
}
@media (max-width: 768px) {
.search-section {
width: 100%;
}
}
</style>

View File

@ -15,7 +15,7 @@
</div> </div>
<div style="width: 1400px;"> <div style="width: 100%;max-width: 1600px;">
<div class="category-filter"> <div class="category-filter">
<!-- 新增的需求和商品radio按钮组 --> <!-- 新增的需求和商品radio按钮组 -->
<div class="radio-group-container"> <div class="radio-group-container">
@ -57,18 +57,15 @@
<!-- 产品 --> <!-- 产品 -->
<div class="product-card-container"> <div class="product-card-container">
<productCard v-if="productList.length > 0" type="supplyAndDemandSquare" :publish_type="publish_type" :productList="productList"> <productCard v-if="productList.length > 0" type="supplyAndDemandSquare" :publish_type="publish_type"
:productList="productList">
</productCard> </productCard>
<div v-else class="no-data"> <div v-else class="no-data">
<img style="width: 150px;height: 10px;" src="./img/empty.svg" alt=""> <img style="width: 150px;height: 10px;" src="./img/empty.svg" alt="">
暂无数据 暂无数据
</div> </div>
</div> </div>
<!-- 返回 -->
<!-- <div class="back-btn" @click="$router.push('/ncmatchHome/index')">
<span>
返回 </span>
</div> -->
</div> </div>
</div> </div>
</div> </div>
@ -80,7 +77,7 @@ import { mapGetters, mapState } from 'vuex'
export default { export default {
name: 'supplyAndDemandSquare', name: 'supplyAndDemandSquare',
components: { components: {
productCard: () => import('@/views/homePage/ncmatch/mainPage/productCard/index.vue') productCard: () => import('@/views/homePage/ncmatch/mainPage/productCard/index.vue'),
}, },
data() { data() {
return { return {
@ -92,36 +89,37 @@ export default {
selectedCompanies: [], // selectedCompanies: [], //
categories: [], categories: [],
companies: [], companies: [],
productList: [] productList: [],
detailVisible: false
} }
}, },
created() { created() {
this.initAllData() this.initAllData()
}, },
computed: { computed: {
isNcmatchHome() { isNcmatchHome() {
return window.location.href.includes('ncmatchHome') return window.location.href.includes('ncmatchHome')
},
...mapGetters(["sidebar", "avatar", "device"]),
...mapState({
isShowPanel: (state) => state.product.showHomeNav,
navIndex: (state) => state.product.navIndex,
gridObj: state => state.operationAnalysis.gridObj,
mybalance: state => state.user.mybalance,
logoutUrl: state => state.login.logoutUrl,
loginStateVuex: state => state.login.loginState,
logoInfoNew: state => state.product.logoInfoNew,
}),
loginState() {
const userId = sessionStorage.getItem('userId');
return this.loginStateVuex || (userId !== null && userId !== 'null' && userId !== '');
},
}, },
...mapGetters(["sidebar", "avatar", "device"]),
...mapState({
isShowPanel: (state) => state.product.showHomeNav,
navIndex: (state) => state.product.navIndex,
gridObj: state => state.operationAnalysis.gridObj,
mybalance: state => state.user.mybalance,
logoutUrl: state => state.login.logoutUrl,
loginStateVuex: state => state.login.loginState,
logoInfoNew: state => state.product.logoInfoNew,
}),
loginState() {
const userId = sessionStorage.getItem('userId');
return this.loginStateVuex || (userId !== null && userId !== 'null' && userId !== '');
},
},
methods: { methods: {
initAllData() { initAllData() {
// selectedCategory // selectedCategory

View File

@ -1,6 +1,6 @@
export function getHomePath() { export function getHomePath() {
// let homePath= "/homePage/index" let homePath= "/homePage/index"
let homePath= "/ncmatchHome/index" // let homePath= "/ncmatchHome/index"
// 业主机构信息 // 业主机构信息
let url_link = window.location.href || ''; let url_link = window.location.href || '';
@ -24,5 +24,5 @@ export function getHomePath() {
} }
} }
console.log("res是",homePath) console.log("res是",homePath)
return homePath return "/homePage/index"
} }