Browse Source

first commit

master
王露 1 year ago
commit
5a7a9664ca
78 changed files with 7316 additions and 0 deletions
  1. 1
    0
      .env
  2. 2
    0
      .env.development
  3. 2
    0
      .env.production
  4. 24
    0
      .gitignore
  5. 3
    0
      .vscode/extensions.json
  6. 7
    0
      README.md
  7. 16
    0
      index.html
  8. 2038
    0
      package-lock.json
  9. 35
    0
      package.json
  10. 26
    0
      postcss.config.cjs
  11. BIN
      public/logo.png
  12. 57
    0
      src/App.vue
  13. 45
    0
      src/apis/index.js
  14. BIN
      src/assets/code.png
  15. BIN
      src/assets/icon-no-data.png
  16. BIN
      src/assets/page-back.png
  17. BIN
      src/assets/phone.png
  18. 25
    0
      src/components/mobile/MBanner.vue
  19. 49
    0
      src/components/mobile/MStickBlock.vue
  20. 124
    0
      src/components/mobile/MTopNav.vue
  21. 35
    0
      src/components/mobile/PageBack.vue
  22. 95
    0
      src/components/mobile/TipDialog.vue
  23. 31
    0
      src/components/pc/Banner.vue
  24. 127
    0
      src/components/pc/FooterInfo.vue
  25. 49
    0
      src/components/pc/StickBlock.vue
  26. 89
    0
      src/components/pc/TipDialogPC.vue
  27. 117
    0
      src/components/pc/TopNav.vue
  28. 21
    0
      src/main.js
  29. 200
    0
      src/router/index.js
  30. 10
    0
      src/store/index.js
  31. 27
    0
      src/store/module/home.js
  32. 55
    0
      src/styles/reset.scss
  33. 69
    0
      src/styles/variables.scss
  34. 103
    0
      src/utils/common.js
  35. 151
    0
      src/utils/request.js
  36. 217
    0
      src/views/mobile/ApplyForEntry.vue
  37. 59
    0
      src/views/mobile/aboutMe/About.vue
  38. 75
    0
      src/views/mobile/aboutMe/AboutMe.vue
  39. 73
    0
      src/views/mobile/home/Home.vue
  40. 43
    0
      src/views/mobile/home/policy/DetailPolicy.vue
  41. 65
    0
      src/views/mobile/home/policy/OtherPolicy.vue
  42. 63
    0
      src/views/mobile/home/policy/PicturePolicy.vue
  43. 44
    0
      src/views/mobile/home/policy/Policy.vue
  44. 51
    0
      src/views/mobile/home/project/IndustrialCluster.vue
  45. 51
    0
      src/views/mobile/home/project/IndustrialPark.vue
  46. 92
    0
      src/views/mobile/home/project/Introduce.vue
  47. 70
    0
      src/views/mobile/industry/Cluster.vue
  48. 67
    0
      src/views/mobile/industry/Enterprise.vue
  49. 95
    0
      src/views/mobile/industry/IndustryPark.vue
  50. 192
    0
      src/views/mobile/policy/PreferentialPolicy.vue
  51. 70
    0
      src/views/mobile/policy/PreferentialPolicyDetail.vue
  52. 62
    0
      src/views/mobile/project/InvestmentProjects.vue
  53. 60
    0
      src/views/mobile/project/Project.vue
  54. 49
    0
      src/views/pc/aboutMe/About.vue
  55. 137
    0
      src/views/pc/aboutMe/AboutMe.vue
  56. 215
    0
      src/views/pc/apply/ApplyForEntry.vue
  57. 74
    0
      src/views/pc/apply/apply.js
  58. 68
    0
      src/views/pc/home/Home.vue
  59. 130
    0
      src/views/pc/home/home.js
  60. 45
    0
      src/views/pc/home/policy/DetailPolicy.vue
  61. 68
    0
      src/views/pc/home/policy/OtherPolicy.vue
  62. 76
    0
      src/views/pc/home/policy/PicturePolicy.vue
  63. 47
    0
      src/views/pc/home/policy/Policy.vue
  64. 40
    0
      src/views/pc/home/project/IndustrialCluster.vue
  65. 104
    0
      src/views/pc/home/project/Introduce.vue
  66. 79
    0
      src/views/pc/home/project/Project.vue
  67. 75
    0
      src/views/pc/home/project/ProjectItem.vue
  68. 65
    0
      src/views/pc/industry/Cluster.vue
  69. 100
    0
      src/views/pc/industry/Enterprise.vue
  70. 100
    0
      src/views/pc/industry/IndustryPark.vue
  71. 109
    0
      src/views/pc/industry/getRegistrationPackageList.js
  72. 215
    0
      src/views/pc/policy/PreferentialPolicy.vue
  73. 60
    0
      src/views/pc/policy/PreferentialPolicyDetail.vue
  74. 83
    0
      src/views/pc/policy/policy.js
  75. 121
    0
      src/views/pc/project/InvestmentProjects.vue
  76. 74
    0
      src/views/pc/project/Project.vue
  77. 23
    0
      src/views/pc/project/project.js
  78. 77
    0
      vite.config.js

+ 1
- 0
.env View File

@@ -0,0 +1 @@
VITE_BASE_URL=http://parkshuyuan.test.hhrchina.com

+ 2
- 0
.env.development View File

@@ -0,0 +1,2 @@
VITE_NODE_ENV=development
VITE_BASE_URL=http://parkshuyuan.test.hhrchina.com

+ 2
- 0
.env.production View File

@@ -0,0 +1,2 @@
VITE_NODE_ENV=production
VITE_BASE_URL=http://parkshuyuan.test.hhrchina.com

+ 24
- 0
.gitignore View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

+ 3
- 0
.vscode/extensions.json View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

+ 7
- 0
README.md View File

@@ -0,0 +1,7 @@
# Vue 3 + Vite

This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.

## Recommended IDE Setup

- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).

+ 16
- 0
index.html View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>书院</title>
</head>

<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>

</html>

+ 2038
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 35
- 0
package.json View File

@@ -0,0 +1,35 @@
{
"name": "SHUYUAN-TOWN",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"amfe-flexible": "^2.2.1",
"axios": "^1.4.0",
"codemirror": "^5.65.14",
"element-plus": "^2.3.4",
"marked": "^3.0.8",
"pinia": "^2.0.35",
"pinia-plugin-persist": "^1.0.0",
"postcss-pxtorem": "^6.0.0",
"qs": "^6.11.2",
"simplemde": "^1.11.2",
"vant": "^4.6.2",
"vue": "^3.2.47",
"vue-router": "^4.1.6",
"vue-scrollto": "^2.20.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.1.0",
"sass": "^1.62.1",
"unplugin-auto-import": "^0.15.3",
"unplugin-vue-components": "^0.24.1",
"vite": "^4.3.2",
"vite-plugin-compression": "^0.5.1"
}
}

+ 26
- 0
postcss.config.cjs View File

@@ -0,0 +1,26 @@
module.exports = {
plugins: {
// autoprefixer: {
// overrideBrowserslist: [
// "Android 4.1",
// "iOS 7.1",
// "Chrome > 31",
// "ff > 31",
// "ie >= 8",
// "last 10 versions", // 所有主流浏览器最近10版本用
// ],
// grid: true,
// },
// "postcss-pxtorem": {
// rootValue: 192,
// exclude: /node_modules\/vant|mobile/i, // 排除mobile和vant库
// propList: ["*"],
// selectorBlackList: [".van-"], // 排除移动端使用了vant库
// },
"postcss-pxtorem": {
rootValue: 37.5,
exclude: /node_modules\/element-plus|pc/i, // 排除pc
propList: ["*"],
},
},
};

BIN
public/logo.png View File


+ 57
- 0
src/App.vue View File

@@ -0,0 +1,57 @@
<template>
<template v-if="!isMobile">
<StickBlock />
<template v-if="!loading">
<TopNav :logo="homeData.logo" />
<div class="main_content">
<Banner :banner="homeData.topBanner"></Banner>
<router-view></router-view>
</div>
<FooterInfo />
</template>
</template>
<template v-else>
<PageBack />
<MobileStickBlock></MobileStickBlock>
<template v-if="!loading">
<MobileTopNav :logo="homeData.logo" />
<MobileBanner :banner="homeData.topBanner" />
<MobileTipDialog ref="tipRef"></MobileTipDialog>
<router-view></router-view>
</template>
</template>
</template>
<script setup>
import TopNav from "@/components/pc/TopNav.vue";
import Banner from "@/components/pc/Banner.vue";
import FooterInfo from "@/components/pc/FooterInfo.vue";
import StickBlock from "@/components/pc/StickBlock.vue";

import MobileBanner from "@/components/mobile/MBanner.vue";
import MobileTopNav from "@/components/mobile/MTopNav.vue";
import MobileTipDialog from "@/components/mobile/TipDialog.vue";
import MobileStickBlock from "@/components/mobile/MStickBlock.vue";
import PageBack from "@/components/mobile/PageBack.vue";

const isMobile = /Android|webOS|iPhone|iPod|BlackBerry|iPad/i.test(navigator.userAgent);
let homeData = ref({});
const loading = ref(true);

import { getHomeData } from "./views/pc/home/home";
import { onMounted, ref } from "vue";
import useHomeStore from "@/store/module/home";
const homeStore = useHomeStore();
const { getData } = getHomeData();

onMounted(async () => {
loading.value = true;
homeData = await getData();
homeStore.homeData.value = homeData;
loading.value = false;
});
</script>
<style lang="scss" scoped>
.main_content {
min-height: 200px;
}
</style>

+ 45
- 0
src/apis/index.js View File

@@ -0,0 +1,45 @@
/**
* api接口统一管理
*/
import {get, post } from "../utils/request";

// 首页-获取详情
export const getAreaData = () => get("/areaInfoManagement/getAreaData");
// 增加官网浏览量
export const updateAreaInfoManagementPageView = p =>
post(`/areaInfoManagement/updateAreaInfoManagementPageView/${p}`);
// 获取最近那一个编辑或者上架的招商载体
export const getRegistrationPackageByFirst = () =>
get("/registrationPackage/getRegistrationPackageByFirst");

// 获取全部产业聚集
export const getAreaIndustrialAllocationList = () =>
get("/areaIndustrialAllocation/getAreaIndustrialAllocationList");
// 获取全部招商载体
export const getRegistrationPackageList = () =>
get("/registrationPackage/getRegistrationPackageList");
// 获取全部企业链家
export const getEnterpriseHomeLinkList = () => get("/enterpriseHomeLink/getEnterpriseHomeLinkList");

// 获取全部招商项目
export const getAttractInvestmentProjectList = () =>
get("/attractInvestmentProject/getAttractInvestmentProjectList");

// 申请入驻接口
export const addEnter = p =>
post(`/enter/addEnter`, p, {
headers: {
"Content-Type": "application/json;charset=UTF-8",
},
});

// 获取全部政策
export const getReleasePolicyList = p => get(`/releasePolicy/getReleasePolicyList`, p);
export const getReleasePolicyById = p => get(`/releasePolicy/getReleasePolicyById`, p);
// 获取某个实体属性状态列表
export const getPickListByKey = p => get(`/common/getPickListByKey`, p);
// 增加浏览量
export const updateReleasePolicyPageView = p =>
post(`/releasePolicy/updateReleasePolicyPageView/${p}`);

export const getContactInfoConfig = p => get(`/contactInfoConfig/getContactInfoConfigByOne`, p);

BIN
src/assets/code.png View File


BIN
src/assets/icon-no-data.png View File


BIN
src/assets/page-back.png View File


BIN
src/assets/phone.png View File


+ 25
- 0
src/components/mobile/MBanner.vue View File

@@ -0,0 +1,25 @@
<template>
<van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
<van-swipe-item v-for="(item, index) in propsData.banner" :key="index">
<img :src="formatImg(item)" alt="banner图片" />
</van-swipe-item>
</van-swipe>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";
const propsData = defineProps({
banner: {
type: Array,
},
});
</script>
<style lang="scss" scoped>
.my-swipe .van-swipe-item {
height: 150px;
img {
@include font(12px, $color-black);
width: 100%;
height: 100%;
}
}
</style>

+ 49
- 0
src/components/mobile/MStickBlock.vue View File

@@ -0,0 +1,49 @@
<template>
<div class="block" @click="showTipInfo">
<div class="block-item">
<img src="@/assets/phone.png" alt="电话咨询" />
<span>电话咨询</span>
</div>
<div class="block-item">
<img src="@/assets/code.png" alt="扫码关注" />
<span>扫码关注</span>
</div>
</div>
<TipDialog ref="tipRef"></TipDialog>
</template>
<script setup>
import TipDialog from "@/components/mobile/TipDialog.vue";

const tipRef = ref(null);
const showTipInfo = () => {
tipRef.value.showTip();
};
</script>
<style lang="scss" scoped>
.block {
position: fixed;
right: 0;
bottom: 100px;
background: rgba(0, 153, 255, 1);
z-index: 1008;
border-radius: 24px;
padding: 20px 0;
@include border-box;
&-item {
@include size(52px, auto);
@include font(10px, $color-white);
@include flex(column, center, center, nowrap);
border-top: 1px solid rgba(255, 255, 255, 1);
padding: 10px 0;
@include border-box;
cursor: pointer;
img {
height: 25px;
margin-bottom: 4px;
}
}
&-item:last-child {
border-bottom: 1px solid rgba(255, 255, 255, 1);
}
}
</style>

+ 124
- 0
src/components/mobile/MTopNav.vue View File

@@ -0,0 +1,124 @@
<!-- 导航 -->
<template>
<div class="header">
<van-sticky>
<div class="wrap">
<section class="wrap_left">
<img v-if="propsData.logo" :src="formatImg(logoArr[0])" alt="logo" />
<div class="title">
<span class="ch">浦东新区书院镇</span>
<span class="en">SHUYUAN TOWN</span>
</div>
</section>
<van-dropdown-menu class="wrap_right" ref="menuRef">
<van-dropdown-item>
<template #title>
<van-icon name="wap-nav" size="26px" />
</template>

<router-link
v-for="item in nav"
:key="item.name"
:to="item.path"
class="routers"
@click="goPath(item.name)"
>
{{ item.meta.title }}
</router-link>
</van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
</div>
</template>

<script setup>
import { useRouter } from "vue-router"; //1.引入路由
const router = useRouter(); //2.实例化路由
const nav = router.getRoutes().filter(item => item.meta.isNav && item.meta.type === "mobile");
const menuRef = ref(null);
const goPath = name => {
router.push({
name,
});
menuRef.value.close();
};

import { formatImg } from "@/utils/common.js";
let logoArr = ref([]);
const propsData = defineProps({
logo: {
type: String,
},
});
logoArr.value = propsData.logo ? JSON.parse(propsData.logo) : [];
</script>
<style lang="scss" scoped>
//@import url(); 引入公共css类
.header {
// @include size(100%, 48px);
// margin: 0 auto;
// color: $color-black;
// background: #fff;
// @include border-box;
}
.wrap {
padding: 10px 12px;
@include size(100%, 48px);
background: #fff;
@include flex(row, space-between, center, wrap);
@include border-box;
.wrap_left {
@include flex(row, center, center, wrap);
img {
@include size(auto, 30px);
margin-right: 10px;
}
.title {
@include size(auto, 30px);
@include flex(column, space-around, flex-start, nowrap);
}
& .ch {
@include font(14px, $color-black);
font-weight: bold;
}
& .en {
@include font(12px, $color-black);
}
}
.wrap_right {
@include size(auto, 100%);
@include flex(row, flex-end, center, wrap);
.routers {
@include size(90%, auto);
margin: auto;
padding: 10px 0;
@include font(14px, $color-black);

@include flex(row, center, center, nowrap);
text-align: center;
// border-bottom: 1px solid rgba(179, 179, 179, 1);
@include border-box;
}
.router-link-active {
color: rgba(0, 153, 255, 1);
}
}
.van-dropdown-item__content > .routers + .routers {
border-top: 1px solid rgba(179, 179, 179, 1);
@include border-box;
}
}
:deep() {
.van-dropdown-menu__bar {
height: 100%;
box-shadow: none;
}
.van-dropdown-menu__title:after {
display: none;
}
.van-dropdown-item {
top: 48px;
}
}
</style>

+ 35
- 0
src/components/mobile/PageBack.vue View File

@@ -0,0 +1,35 @@
<template>
<div class="page-back" v-if="$route.path !== '/m_home'">
<div class="icon-back" @click="goBack">
<img src="@/assets/page-back.png" />
</div>
</div>
</template>
<script setup>
import { useRoute, useRouter } from "vue-router"; //1.引入路由
const $router = useRouter(); //2.实例化路由
const $route = useRoute(); //2.实例化路由
const goBack = () => {
if ($route.path == "/m_preferential-policy-detail") {
$router.go(-1);
} else {
$router.push({
path: "/",
});
}
};
</script>
<style lang="scss" scoped>
.icon-back {
width: 38px;
height: 38px;
position: fixed;
left: 10px;
top: 48px;
z-index: 999;
img {
width: 100%;
height: 100%;
}
}
</style>

+ 95
- 0
src/components/mobile/TipDialog.vue View File

@@ -0,0 +1,95 @@
<template>
<van-dialog v-model:show="dialogVisible" :showConfirmButton="false">
<template #title>
<div class="head">
<van-icon name="cross" size="20px" @click="dialogVisible = false" />
</div>
<div>立即咨询</div>
</template>
<div class="dialog">
<div class="dialog-item">
<span class="dialog-item-title">电话咨询</span>
<div class="phone">
<p v-if="contract.mobileOne">{{ contract.mobileOne }}</p>
<p v-if="contract.mobileTwo">{{ contract.mobileTwo }}</p>
<p v-if="contract.mobileThree">{{ contract.mobileThree }}</p>
</div>
</div>
<div class="dialog-item" v-if="contract.qrCode.length > 0">
<span class="dialog-item-title">扫码咨询</span>
<img :src="formatImg(contract.qrCode[0])" alt="" />
</div>

<div class="dialog-item" v-if="contract.email">
<span class="dialog-item-title">邮箱</span>
<span class="email">{{ contract.email }}</span>
</div>
</div>
</van-dialog>
</template>

<script setup>
import { getContactInfoConfig } from "@/apis/index";
import { toast, formatImg } from "@/utils/common.js";
import { onMounted, ref } from "vue";

const dialogVisible = ref(false);
const handleClose = () => {
dialogVisible.value = false;
};
const showTip = () => {
dialogVisible.value = true;
};
defineExpose({ showTip, handleClose });
const contract = ref();
const getData = () => {
getContactInfoConfig()
.then(res => {
if (res.data.status == 0) {
contract.value = res.data.data;
} else {
toast(res.data.msg, "error");
}
})
.catch(error => {
console.log(error);
});
};
onMounted(() => {
getData();
});
</script>

<style lang="scss" scoped>
.head {
text-align: right;
padding: 0 10px;
@include border-box;
}
.dialog {
@include flex(column, flex-start, center, nowrap);
padding-bottom: 20px;
@include border-box;

&-item {
@include flex(column, flex-start, center, nowrap);
&-title {
@include font(14px, $color-black);
font-weight: 400;
margin: 12px 0;
}
.phone {
@include font(14px, rgba(0, 153, 255, 1));
}
p {
margin-bottom: 10px;
}
img {
@include size(150px, 150px);
}
.email {
@include font(14px, $color-black);
}
}
}
</style>

+ 31
- 0
src/components/pc/Banner.vue View File

@@ -0,0 +1,31 @@
<template>
<el-carousel :interval="5000" arrow="always">
<el-carousel-item v-for="(item, index) in propsData.banner" :key="index">
<img :src="formatImg(item)" alt="banner图片" />
</el-carousel-item>
</el-carousel>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";
const propsData = defineProps({
banner: {
type: Array,
},
});
</script>
<style lang="scss" scoped>
.el-carousel--horizontal {
height: 700px;
min-width: $wrapWidth;
// background: yellowgreen;
}
:deep() {
.el-carousel__container {
height: 100%;
img {
// background: pink;
@include size(100%, 100%);
}
}
}
</style>

+ 127
- 0
src/components/pc/FooterInfo.vue View File

@@ -0,0 +1,127 @@
<!-- 底部导航 -->
<template>
<el-row class="footer_box">
<div class="wrap_box">
<div class="nav_info">
<section class="nav">
<div class="title">浦东新区书院镇</div>
<div class="navs">
<router-link v-for="item in nav" :key="item.name" :to="item.path">
{{ item.meta.title }}
</router-link>
</div>
</section>

<section class="footer_info">
<img v-if="qrCode" :src="formatImg(qrCode)" alt="" />
<div class="info">
<span>地址:{{ addressConfig }}</span>
<span>电话:{{ mobile }}</span>
<span>传真:{{ faxConfig }}</span>
<span>邮箱:{{ email }}</span>
</div>
</section>
</div>
<div class="line"></div>
<div class="bottom_info">
<span>版权所有 ©2023-现在</span>
<span>{{ addressConfig }}</span>
<span>沪CP畜2022025118号</span>
<span>沪B2-20200177</span>
<span>沪公网安备31010402001838号</span>
</div>
</div>
</el-row>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";
import { useRouter } from "vue-router"; //1.引入路由
const router = useRouter(); //2.实例化路由
const nav = router.getRoutes().filter(item => item.meta.isNav && item.meta.type === "pc");
import useHomeStore from "@/store/module/home";
const homeStore = useHomeStore();
const qrCode = homeStore.contract.value.qrCode[0];
const email = homeStore.contract.value.email;
const mobile = homeStore.contract.value.mobileOne;
const faxConfig = homeStore.contract.value.faxConfig;
const addressConfig = homeStore.contract.value.addressConfig;
</script>
<style lang="scss" scoped>
//@import url(); 引入公共css类
.footer_box {
@include size(100%, 305Px);
background: url(https://img.js.design/assets/img/64a77155c194a91b49b37dcc.jpg) no-repeat;
background-size: 100% 100%;
box-sizing: border-box;
@include flex(row, center, center, wrap);
}
.wrap_box {
width: 1200Px;
}
.nav_info {
@include size(1200Px, 250Px);
@include flex(row, space-between, center, nowrap);
// padding: 0 50Px;
box-sizing: border-box;
}
.nav {
@include border-box;
.title {
@include font(36Px, $color-white);
margin-bottom: 54Px;
}
.navs {
@include flex(row, flex-start, center, nowrap);
margin: 0 auto;
@include font(16Px, $color-white);
a {
@include font(16Px, $color-white);
margin-right: 20Px;
}

// a:hover {
// color: #99a9b8;
// }
// .router-link-active {
// color: #99a9b8;
// }
}
.navs > a + a {
border-left: 1Px solid $color-white;
padding-left: 20Px;
}
}
.footer_info {
@include size(auto, 170Px);
@include flex(row, space-between, center, nowrap);
img {
@include size(170Px, 170Px);
margin-right: 10Px;
}
.info {
@include size(auto, 170Px);
@include font(16Px, $color-white);
@include flex(column, space-between, flex-start, nowrap);
> div {
flex: 1;
@include flex(column, center, flex-start, nowrap);
span {
margin-bottom: 4Px;
}
}
}
}
.line {
width: 100%;
height: 1Px;
background: $color-white;
}
.bottom_info {
@include size(1200Px, 55Px);
@include font(14Px, $color-white);
font-weight: 500;
// padding: 0 50Px;
box-sizing: border-box;
@include flex(row, space-between, center, nowrap);
}
</style>

+ 49
- 0
src/components/pc/StickBlock.vue View File

@@ -0,0 +1,49 @@
<template>
<div class="block" @click="showTipInfo">
<div class="block-item">
<img src="@/assets/phone.png" alt="电话咨询" />
<span>电话咨询</span>
</div>
<div class="block-item">
<img src="@/assets/code.png" alt="扫码关注" />
<span>扫码关注</span>
</div>
</div>
<el-backtop :bottom="100"></el-backtop>
<TipDialog ref="tipRefPC"></TipDialog>
</template>
<script setup>
import TipDialog from "@/components/pc/TipDialogPC.vue";
const tipRefPC = ref(null);
const showTipInfo = () => {
tipRefPC.value.showTip();
};
</script>
<style lang="scss" scoped>
.block {
position: fixed;
right: 0;
top: 200px;
background: rgba(0, 153, 255, 1);
z-index: 1008;
border-radius: 45px;
padding: 50px 0;
@include border-box;
&-item {
@include size(100px, auto);
@include font(16px, $color-white);
@include flex(column, center, center, nowrap);
border-top: 1px solid rgba(255, 255, 255, 1);
padding: 10px 0;
@include border-box;
cursor: pointer;
img {
height: 60px;
margin-bottom: 4px;
}
}
&-item:last-child {
border-bottom: 1px solid rgba(255, 255, 255, 1);
}
}
</style>

+ 89
- 0
src/components/pc/TipDialogPC.vue View File

@@ -0,0 +1,89 @@
<template>
<el-dialog
v-model="dialogVisible"
title="立即咨询"
:before-close="handleClose"
center
style="border-radius: 20px"
>
<div class="dialog" v-if="contract">
<div class="dialog-item">
<span class="dialog-item-title">电话咨询</span>
<div class="phone">
<p v-if="contract.mobileOne">{{ contract.mobileOne }}</p>
<p v-if="contract.mobileTwo">{{ contract.mobileTwo }}</p>
<p v-if="contract.mobileThree">{{ contract.mobileThree }}</p>
</div>
</div>
<div class="dialog-item" v-if="contract.qrCode.length > 0">
<span class="dialog-item-title">扫码咨询</span>
<img :src="formatImg(contract.qrCode[0])" alt="" />
</div>
<div class="dialog-item" v-if="contract.email">
<span class="dialog-item-title">邮箱</span>
<span class="email">{{ contract.email }}</span>
</div>
</div>
</el-dialog>
</template>

<script setup>
import { getContactInfoConfig } from "@/apis/index";
import { toast, formatImg } from "@/utils/common.js";
import { onMounted, ref } from "vue";
import useHomeStore from "@/store/module/home";
const homeStore = useHomeStore();

const dialogVisible = ref(false);
const handleClose = () => {
dialogVisible.value = false;
};
const showTip = () => {
dialogVisible.value = true;
};
defineExpose({ showTip, handleClose });
const contract = ref();
const getData = () => {
getContactInfoConfig()
.then(res => {
if (res.data.status == 0) {
contract.value = res.data.data;
homeStore.contract.value = res.data.data;
} else {
toast(res.data.msg, "error");
}
})
.catch(error => {
console.log(error);
});
};
onMounted(() => {
getData();
});
</script>
<style lang="scss" scoped>
.dialog {
@include flex(row, space-between, flex-start, nowrap);
&-item {
width: 30%;
@include flex(column, flex-start, center, nowrap);
&-title {
@include font(20px, $color-black);
font-weight: 400;
margin-bottom: 50px;
}
.phone {
@include font(24px, rgba(0, 153, 255, 1));
}
p {
margin-bottom: 30px;
}
img {
@include size(150px, 150px);
}
.email {
@include font(24px, $color-black);
}
}
}
</style>

+ 117
- 0
src/components/pc/TopNav.vue View File

@@ -0,0 +1,117 @@
<!-- 导航 -->
<template>
<div class="header">
<div class="wrap">
<section class="wrap_left">
<img v-if="propsData.logo" :src="formatImg(logoArr[0])" alt="logo" />
<div class="title">
<span class="ch">浦东新区书院镇</span>
<span class="en">SHUYUAN TOWN</span>
</div>
</section>
<section class="wrap_right">
<div>
<router-link
v-for="item in nav"
:key="item.name"
:to="item.path"
:class="[item.name === 'P_ApplyForEntry' ? 'apply' : '']"
>
{{ item.meta.title }}
</router-link>
</div>
</section>
</div>
</div>
</template>

<script setup>
import { useRouter } from "vue-router"; //1.引入路由
const router = useRouter(); //2.实例化路由
const nav = router.getRoutes().filter(item => item.meta.isNav && item.meta.type === "pc");

import { formatImg } from "@/utils/common.js";
let logoArr = ref([]);
const propsData = defineProps({
logo: {
type: String,
},
});

logoArr.value = propsData.logo ? JSON.parse(propsData.logo) : [];
</script>
<style lang="scss" scoped>
//@import url(); 引入公共css类
.header {
@include size(100%, 100px);
margin: 0 auto;
color: $color-black;
background: #fff;
@include flex(row, center, center, wrap);
@include border-box;
}
.wrap {
min-width: $wrapWidth;
@include size($wrapWidth, 100%);
@include flex(row, space-between, center, wrap);
.wrap_left {
@include flex(row, center, center, wrap);
img {
@include size(auto, 50px);
margin-right: 20px;
}
.title {
@include size(auto, 50px);
@include flex(column, space-around, flex-start, nowrap);
}
& .ch {
@include font(16px, $color-black);
font-weight: bold;
}
& .en {
@include font(12px, $color-black);
}
}
.wrap_right {
height: 100%;
@include flex(row, flex-end, center, wrap);
& > div {
height: 100%;
@include flex(row, space-between, center, wrap);
a {
margin-right: 60px;
@include font(20px, $color-black);
font-weight: bold;
@include flex(row, center, center, wrap);
}
a.apply {
border-radius: 50px;
background: rgba(0, 145, 255, 1);
font-weight: normal;
padding: 2px 12px;
@include font(18px, $color-white);
box-sizing: border-box;
margin-right: 0;
}
:hover {
color: rgba(0, 145, 255, 1);
}
.router-link-active {
color: rgba(0, 145, 255, 1);
}
}
}

.enterprise {
@include size(160px, 40px);
border: solid 1px rgba(255, 255, 255, 0.5);
border-radius: 20px;
@include border-box;
@include flex(row, center, center, wrap);
img {
@include size(20px, 20px);
margin-right: 10px;
}
}
}
</style>

+ 21
- 0
src/main.js View File

@@ -0,0 +1,21 @@
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "@/styles/reset.scss"; /*引入公共样式*/
import "amfe-flexible";

// main.js
if (/Android|webOS|iPhone|iPod|BlackBerry|iPad/i.test(navigator.userAgent)) {
// Vue.use(VueTouch, { name: "v-touch" });
// VueTouch.config.swipe = {
// threshold: 50,
// };
var oMeta = document.createElement("meta");
oMeta.content =
"width=device-width, initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0, user-scalable=0";
oMeta.name = "viewport";
document.getElementsByTagName("head")[0].appendChild(oMeta);
}

createApp(App).use(store).use(router).mount("#app");

+ 200
- 0
src/router/index.js View File

@@ -0,0 +1,200 @@
import { createRouter, createWebHistory } from "vue-router";
const isMobile = /Android|webOS|iPhone|iPod|BlackBerry|iPad/i.test(navigator.userAgent);
const redirectPath = isMobile ? "/m_home" : "/p_home";
const routes = [{
path: "/",
redirect: redirectPath,
},
{
path: "/m_home",
name: "M_Home",
component: () =>
import ("@/views/mobile/home/Home.vue"),
meta: {
title: "首页",
isNav: true,
type: "mobile",
},
},
{
path: "/m_industry-park",
name: "M_IndustryPark",
component: () =>
import ("@/views/mobile/industry/IndustryPark.vue"),
meta: {
title: "产业园区",
isNav: true,
type: "mobile",
},
},
{
path: "/m_investment-projects",
name: "M_InvestmentProjects",
component: () =>
import ("@/views/mobile/project/InvestmentProjects.vue"),
meta: {
title: "招商项目 ",
isNav: true,
type: "mobile",
},
},
{
path: "/m_preferential-policy",
name: "M_PreferentialPolicy",
component: () =>
import ("@/views/mobile/policy/PreferentialPolicy.vue"),
meta: {
title: "优惠政策",
isNav: true,
type: "mobile",
},
},
{
path: "/m_preferential-policy-detail",
name: "M_PreferentialPolicyDetail",
component: () =>
import ("@/views/mobile/policy/PreferentialPolicyDetail.vue"),
meta: {
title: "优惠政策",
type: "mobile",
},
},
{
path: "/m_about-me",
name: "M_AboutMe",
component: () =>
import ("@/views/mobile/aboutMe/AboutMe.vue"),
meta: {
title: "关于书院",
isNav: true,
type: "mobile",
},
},
{
path: "/m_apply-for-entry",
name: "M_ApplyForEntry",
component: () =>
import ("@/views/mobile/ApplyForEntry.vue"),
meta: {
title: "申请入驻",
isNav: true,
type: "mobile",
},
},
{
path: "/p_home",
name: "P_Home",
component: () =>
import ("@/views/pc/home/Home.vue"),
meta: {
title: "首页",
isNav: true,
type: "pc",
},
},
{
path: "/p_industry-park",
name: "P_IndustryPark",
component: () =>
import ("@/views/pc/industry/IndustryPark.vue"),
meta: {
title: "产业园区",
isNav: true,
type: "pc",
},
},
{
path: "/p_investment-projects",
name: "P_InvestmentProjects",
component: () =>
import ("@/views/pc/project/InvestmentProjects.vue"),
meta: {
title: "招商项目 ",
isNav: true,
type: "pc",
},
},
{
path: "/p_preferential-policy",
name: "P_PreferentialPolicy",
component: () =>
import ("@/views/pc/policy/PreferentialPolicy.vue"),
meta: {
title: "优惠政策",
isNav: true,
type: "pc",
},
},
{
path: "/p_preferential-policy-detail",
name: "P_PreferentialPolicyDetail",
component: () =>
import ("@/views/pc/policy/PreferentialPolicyDetail.vue"),
meta: {
title: "优惠政策",
type: "pc",
},
},
{
path: "/p_about-me",
name: "P_AboutMe",
component: () =>
import ("@/views/pc/aboutMe/AboutMe.vue"),
meta: {
title: "关于书院",
isNav: true,
type: "pc",
},
},
{
path: "/p_apply-for-entry",
name: "P_ApplyForEntry",
component: () =>
import ("@/views/pc/apply/ApplyForEntry.vue"),
meta: {
title: "申请入驻",
isNav: true,
type: "pc",
},
},
];

const router = createRouter({
history: createWebHistory("/public"),
routes,
scrollBehavior() {
return {
left: 0,
top: 0,
};
},
});
router.beforeEach((to, from, next) => {
// 移动端访问PC
if (isMobile && to.meta.type !== "mobile") {
let routers = router.options.routes.filter(v => v.path != "/" && v.meta.type === "mobile");
let path = null;
routers.forEach(v => {
if (v.name.split("_")[1] == to.name.split("_")[1]) {
path = v.path;
}
});

if (path) next(path);
next("/");
}
// pc 访问 移动
if (!isMobile && to.meta.type !== "pc") {
const routers = router.options.routes.filter(v => v.path != "/" && v.meta.type === "pc");
let path = null;
routers.forEach(v => {
if (v.name.split("_")[1] == to.name.split("_")[1]) {
path = v.path;
}
});
if (path) next(path);
next("/");
}
next();
});
export default router;

+ 10
- 0
src/store/index.js View File

@@ -0,0 +1,10 @@
import { createPinia } from "pinia";
// 引入持久化插件
import piniaPluginPersist from "pinia-plugin-persist";

const store = createPinia();
// 使用该插件
store.use(piniaPluginPersist);

//导出
export default store;

+ 27
- 0
src/store/module/home.js View File

@@ -0,0 +1,27 @@
import { defineStore } from "pinia";
import { ref } from "vue";
const useHomeStore = defineStore("Home", {
state: () => {
return {
homeData: ref({}),
policyInfo: ref({}),
contract: ref({}),
};
},
actions: {},
getters: {},
persist: {
//这里存储默认使用的是session
enabled: true,
strategies: [{
//key的名称
key: "home",
//更改默认存储,我更改为localStorage
storage: localStorage,
// 可以选择哪些进入local存储,这样就不用全部都进去存储了
// 默认是全部进去存储
// paths: [""],
}, ],
},
});
export default useHomeStore;

+ 55
- 0
src/styles/reset.scss View File

@@ -0,0 +1,55 @@
* {
padding: 0;
margin: 0;
}

html {
height: 100%;
}

body {
height: 100%;
}

#app {
width: 100%;
height: 100%;
max-width: auto;
min-height: 100%;
}

ul,
ol,
li {
list-style: none;
}

a {
text-decoration: none;
}

.pointer {
cursor: pointer;
}

.fl {
float: left;
}

.fr {
float: right;
}

.tip {
color: #fff;
font-size: 16px;
text-align: center;
background: rgba(0, 0, 0, 0.7);
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 14px;
box-sizing: content-box;
}

+ 69
- 0
src/styles/variables.scss View File

@@ -0,0 +1,69 @@
$bg-color: red;
$color-white: #fff;
$color-black: rgba(0, 0, 0, 1);
$wrapWidth: 1200px;
$contentWidth: 1180px;
// 尺寸
@mixin size($w, $h) {
width: $w;
height: $h;
}

// 字体大小、颜色
@mixin font($font-size, $font-color) {
font-size: $font-size;
color: $font-color;
}

// 背景图片
@mixin bg-image($url) {
background-image: url($url);
}

// 文本溢出省略显示
// 单行文本溢出省略显示
@mixin text-ellipsis() {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

// 多行文本溢出省略显示,支持 WebKit浏览器或移动端的页面
// $row - 行数
@mixin text-ellipsis-multiple($row) {
overflow: hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: $row;
-webkit-box-orient: vertical;
}

// flex布局
@mixin flex( $direction: row, $justify-content: flex-start, $align-items: flex-start, $flex-wrap: nowrap) {
display: flex;
flex-direction: $direction;
justify-content: $justify-content;
align-items: $align-items;
flex-wrap: $flex-wrap;
}

@mixin hoverLine($height, $color: $color-text-primary) {
position: relative;
&:hover::after {
content: "";
position: absolute;
height: $height;
width: 100%;
background-color: $color;
bottom: 0;
left: 0;
}
}

// IE盒模型
@mixin border-box {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}

+ 103
- 0
src/utils/common.js View File

@@ -0,0 +1,103 @@
import router from "../router/index";
import dayjs from "dayjs"; // 时间格式化组件
import { showToast } from "vant";

const { VITE_BASE_URL } =
import.meta.env;
// 在新的页面打开路由
export const routerOpenInNewWindow = routerPath => {
let routeData = router.resolve(routerPath);
window.open(routeData.href, "_blank");
};

/**
*
* @param {*} path 图片路径
* @returns
*/
export const formatImg = path => {
return path ? `${VITE_BASE_URL}/common/getImg?path=${path}` : "";
// return path ? `http://shuyuan.test.hhrchina.com/filex/img/${path}` : "";
};

/**
*
* @param {*} path 图片路径
* @returns
*/
export const formatVideo = path => {
return path ? `${VITE_BASE_URL}/common/getVideo?path=${path}` : "";
};

/**
*
* @param {*} time 时间
* @param {*} fmtString 转换的格式
* @returns
*/
export const formatDate = (time, fmtString) => {
time = new Date(time);
// 使用dayjs这个日期格式化类库实现日期的格式化功能
if (time == "" || !time) {
return "——";
} else {
return dayjs(time).format(fmtString);
}
};

/**
*
* @param {*} ary 数组
* @param {*} key 根据key值排序
* @returns
*/
export const sortByKey = (ary, key) => {
return ary.sort(function(a, b) {
let x = a[key];
let y = b[key];
return x < y ? -1 : x > y ? 1 : 0;
});
};

/**
*
* @param {*} val string
* @returns
*/
export const toast = (val, type) => {
const isMobile = /Android|webOS|iPhone|iPod|BlackBerry|iPad/i.test(navigator.userAgent);
if (isMobile) {
showToast({
message: val,
className: "tip",
});
} else {
ElMessage({
message: val,
type,
});
}
};

export const debounce = (func, delay) => {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
};

export const throttle = (func, wait) => {
let timer;
return () => {
if (timer) {
return;
}
timer = setTimeout(() => {
func();
timer = null;
}, wait);
};
};

+ 151
- 0
src/utils/request.js View File

@@ -0,0 +1,151 @@
/**axios封装
* 请求拦截、相应拦截、错误统一处理
*/
import axios from "axios";
// import QS from 'qs';

// 环境的切换
axios.defaults.baseURL =
import.meta.env.VITE_BASE_URL;

// 请求超时时间
axios.defaults.timeout = 25000;

// post请求头
axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8";
// axios.defaults.headers.post["Content-Type"] = "application/json; charset=UTF-8";

let pending = []; //声明一个数组用于存储每个请求的取消函数和axios标识
let cancelRepeatUrl = []; // 园区服务、物业租售
let cancelToken = axios.CancelToken;
let removePending = config => {
// console.log(config);
for (let i in pending) {
if (pending[i].url === axios.defaults.baseURL + config.url) {
//在当前请求在数组中存在时执行取消函数
pending[i].f(); //执行取消操作
// pending.splice(i, 1); // 根据具体情况决定是否在这里就把pending去掉
// console.log("重复的:" + pending[i].url);
}
}
};

// 请求拦截器
axios.interceptors.request.use(
config => {
// 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
// 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
// const token = store.state.token;
// token && (config.headers.Authorization = token);
// return config;
removePending(config); //在一个axios发送前执行一下判定操作,在removePending中执行取消操作
// console.log(config.url);
config.cancelToken = new cancelToken(function executor(c) {
//本次axios请求的配置添加cancelToken
if (cancelRepeatUrl.indexOf(config.url) > -1) {
pending.push({
// url: config.url,
url: axios.defaults.baseURL + config.url,
f: c,
});
// console.log(axios.defaults.baseURL + config.url);
//将本次的url添加到pending中,因此对于某个url第一次发起的请求不会被取消,因为还没有配置取消函数
}
});
return Promise.resolve(config);
},
error => {
return Promise.error(error);
}
);

// 响应拦截器
axios.interceptors.response.use(
response => {
if (response.status === 200) {
// removePending(response.config); //在一个axios响应后再执行一下取消操作,把已经完成的请求从pending中移除
return Promise.resolve(response);
} else {
return Promise.reject(response);
}
},
// 服务器状态码不是200的情况
error => {
if (error.response) {
return Promise.reject(error.response);
}
}
);
/**
* get方法,对应get请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
export function get(url, params) {
return new Promise((resolve, reject) => {
axios
.get(url, {
params: params,
})
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
});
}
/**
* post方法,对应post请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
export function post(url, params, options) {
return new Promise((resolve, reject) => {
axios
.post(url, params, options)
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
});
}

/**
* put方法,对应put请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
export function put(url, params) {
return new Promise((resolve, reject) => {
axios
.put(url, params)
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
});
}

/**
* delete方法,对应delete请求,delete是保留字,使用del代替
* @param {String} url [请求的url地址]
*/
export function del(url, params) {
return new Promise((resolve, reject) => {
axios
.delete(url, {
params: params,
})
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
});
}

+ 217
- 0
src/views/mobile/ApplyForEntry.vue View File

@@ -0,0 +1,217 @@
<template>
<section>
<div class="form-tab">
<span
v-for="(item, index) in tabArr"
:class="[currentTb == index ? 'active' : '']"
@click="changeTab(item.value, index)"
:key="item.value"
>
{{ item.label }}
</span>
</div>
<div class="form">
<el-form
v-if="currentTb == 0"
:label-position="labelPosition"
label-width="100px"
:model="params"
>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
企业名称
</div>
</template>
<el-input
class="form-item-content"
v-model="params.companyName"
placeholder="请输入企业名称"
maxlength="50"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
联系人
</div>
</template>
<el-input
class="form-item-content"
v-model="params.name"
placeholder="请输入联系人名称"
maxlength="10"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
联系电话
</div>
</template>
<el-input
class="form-item-content"
v-model="params.mobile"
placeholder="请输入联系电话"
maxlength="11"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">预计产税</div>
</template>
<el-input
class="form-item-content"
v-model="params.estimatedPropertyTax"
placeholder="请输入预计产税"
maxlength="20"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label><div class="form-item-label">需求内容</div></template>
<el-input
class="form-item-content"
v-model="params.text"
type="textarea"
rows="10"
placeholder="请输入需求内容"
resize="none"
maxlength="200"
/>
</el-form-item>
</el-form>

<el-form
v-else
:label-position="labelPosition"
label-width="100px"
:model="formLabelAlign"
>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
企业名称
</div>
</template>
<el-input
class="form-item-content"
v-model="params.companyName"
placeholder="请输入企业名称"
maxlength="50"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
联系人
</div>
</template>
<el-input
class="form-item-content"
v-model="params.name"
placeholder="请输入联系人名称"
maxlength="10"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
联系电话
</div>
</template>
<el-input
class="form-item-content"
v-model="params.mobile"
placeholder="请输入联系电话"
maxlength="11"
/>
</el-form-item>
</el-form>
<van-button
class="submit"
round
block
type="primary"
native-type="submit"
@click="submitForm"
>
提交申请
</van-button>

<div class="text-tip">提交后我们将在1-7个工作日内联系您</div>
</div>
</section>
</template>
<script setup>
import { apply } from "@/views/pc/apply/apply";
const { labelPosition, currentTb, tabArr, params, applyForEntry, changeTab, submitForm } = apply();
</script>
<style lang="scss" scoped>
.form-tab {
padding: 10px 22px;
@include border-box;
@include flex(row, space-between, center, nowrap);
span {
width: 50%;
height: 30px;
@include font(12px, $color-black);
font-weight: 500;
@include flex(row, center, center, nowrap);
border-radius: 20px;
background: rgba(245, 245, 245, 1);
cursor: pointer;
@include border-box;
}
.active {
@include font(12px, $color-white);
background: rgba(0, 153, 255, 1);
}
}
.form {
margin: 10px 22px;
padding: 18px;
background: rgba(255, 255, 255, 1);
border-radius: 20px;
@include border-box;
text-align: center;

&-item {
border-radius: 50px;
background: rgba(242, 244, 249, 1);
padding: 5px 10px 5px 0;
@include border-box;
&-label {
@include font(12px, $color-black);
border-right: 3px solid rgba(145, 145, 145, 1);
padding-right: 10px;
@include border-box;
}
}
.submit {
@include size(186px, 40px);
@include font(12px, $color-white);
border-radius: 50px;
@include border-box;
margin: 20px auto;
}
.text-tip {
@include font(12px, $color-black);
}
}
.required {
color: rgba(255, 0, 0, 1);
}
:deep() {
.el-input__wrapper,
.el-textarea__inner {
background: none;
box-shadow: none;
}
}
</style>

+ 59
- 0
src/views/mobile/aboutMe/About.vue View File

@@ -0,0 +1,59 @@
<template>
<div class="block">
<div class="title">{{ propsData.aboutItem.label }}</div>
<div class="content" v-html="formatHtml(propsData.aboutItem.value)"></div>

<!-- <div class="content">
<div>
书院镇位于上海市浦东新区的东南部、自贸区临港新片区北门户位置,在浦东国际机场和洋山深水港之间,东临大海,南接泥城镇,西至万祥镇,北与老港镇隔大治河相望。
</div>
<img
src="https://img.js.design/assets/img/64ab9e9276b0da9863e5bba0.png#1b5fe6bef0090a0d2c77afb77c9b7873"
alt=""
/>
</div> -->
</div>
</template>
<script setup>
import SimpleMDE from "simplemde";
const propsData = defineProps({
aboutItem: {
type: Object,
},
});

const formatHtml = text => {
let html = SimpleMDE.prototype.markdown(text);
console.log(html);
let imgUrl = `${import.meta.env.VITE_BASE_URL}/common/getImg?path=`;
html = html.replace(
/<img [^>]*src=['"]([^'"]+)[^>]*>/gi,
"<img src='" + imgUrl + "$1' width='100%'/>"
);
return html;
};
</script>
<style lang="scss" scoped>
.block {
background: rgba(255, 255, 255, 1);
border-radius: 20px;
padding: 10px;
@include border-box;
@include flex(column, space-between, center, nowrap);
margin: 10px 0;
.title {
@include font(14px, $color-black);
font-weight: 500;
margin-bottom: 10px;
}
.content {
@include font(12px, $color-black);
line-height: 20px;
word-break: break-word;
}
img {
@include size(100%, auto);
margin-top: 10px;
}
}
</style>

+ 75
- 0
src/views/mobile/aboutMe/AboutMe.vue View File

@@ -0,0 +1,75 @@
<template>
<div class="about" v-if="!loading">
<!-- <div>
<span v-for="item in tabArr" :key="item.label">{{ item.label }}</span>
</div> -->
<template v-for="(item, index) in tabArr" :key="item.label">
<About
:aboutItem="{
label: tabArr[index].label,
value: homeData[tabArr[index].key],
}"
></About>
</template>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import About from "./About.vue";
import useHomeStore from "@/store/module/home";
const homeStore = useHomeStore();
let homeData = ref({});
const tab = [
{
label: "区位优势",
value: 0,
key: "regionalAdvantages",
},
{
label: "发展机遇",
value: 1,
key: "developmentOpportunity",
},
{
label: "配套建设",
value: 2,
key: "supportingConstruction",
},
{
label: "发展趋势",
value: 3,
key: "developmentTendency",
},
{
label: "交通优势",
value: 4,
key: "transportationAdvantage",
},
{
label: "镇域概况",
value: 5,
key: "townProfile",
},
{
label: "书院介绍",
value: 6,
key: "academyHistory",
},
];
const tabArr = ref(tab);
const loading = ref(true);

onMounted(() => {
loading.value = true;
homeData.value = homeStore.homeData.value;
loading.value = false;
});
</script>
<style lang="scss" scoped>
.about {
padding: 0 15px;
overflow: hidden;
background: rgba(245, 245, 245, 1);
@include border-box;
}
</style>

+ 73
- 0
src/views/mobile/home/Home.vue View File

@@ -0,0 +1,73 @@
<template>
<div v-if="!loading">
<div class="wrap-box">
<Introduce :homeData="homeData"></Introduce>
<IndustrialPark :projectItem="registrationPackage"></IndustrialPark>
<IndustrialCluster :industrialAllocation="industrialAllocation"></IndustrialCluster>
</div>
<div class="wrap-box">
<div class="title">政策优势</div>
<Policy v-for="(item, index) in policy" :key="index" :policyItem="item"></Policy>
<PicturePolicy
:policySupportKeyIndustries="homeData.policySupportKeyIndustries"
:domesticTalentIntroduction="homeData.domesticTalentIntroduction"
></PicturePolicy>
<DetailPolicy
v-for="(item, index) in detailPolicy"
:key="index"
:detailPolicyItem="item"
></DetailPolicy>
<OtherPolicy></OtherPolicy>
</div>
</div>
</template>
<script setup>
import Introduce from "./project/Introduce.vue";
import IndustrialPark from "./project/IndustrialPark.vue";
import IndustrialCluster from "./project/IndustrialCluster.vue";
import Policy from "./policy/Policy.vue";
import PicturePolicy from "./policy/PicturePolicy.vue";
import DetailPolicy from "./policy/DetailPolicy.vue";
import OtherPolicy from "./policy/OtherPolicy.vue";
import { getHomeData } from "@/views/pc/home/home";
import { onBeforeMount } from "vue";
const {
getAreaIndustrialAllocation,
setDetailPolicy,
setPolicy,
updatePageView,
getRegistrationPackage,
} = getHomeData();
let homeData = ref({});
let industrialAllocation = ref([]);
let policy = ref([]);
let detailPolicy = ref([]);
let registrationPackage = ref({});

const loading = ref(true);
import useHomeStore from "@/store/module/home";
const homeStore = useHomeStore();
onBeforeMount(async () => {
loading.value = true;
homeData.value = homeStore.homeData.value;
policy = setPolicy(homeData.value);
detailPolicy = setDetailPolicy(homeData.value);
industrialAllocation.value = await getAreaIndustrialAllocation();
registrationPackage.value = await getRegistrationPackage();
loading.value = false;
updatePageView(homeData.value.areaInfoManagementId);
});
</script>

<style lang="scss" scoped>
.wrap-box {
padding: 10px 15px;
background: #fff;
@include border-box;
}
.title {
@include font(16px, $color-black);
font-weight: bold;
margin-bottom: 10px;
}
</style>

+ 43
- 0
src/views/mobile/home/policy/DetailPolicy.vue View File

@@ -0,0 +1,43 @@
<template>
<div class="detail-title">{{ propsData.detailPolicyItem.title }}</div>
<div class="detail-policy">
<div
class="detail-policy-content"
v-html="SimpleMDE.prototype.markdown(propsData.detailPolicyItem.content)"
></div>
<!-- <div class="detail-policy-content">
按当年销售金额最高 10% 给予奖励,单款芯片产品年度奖励总额不超过 500 万元。
</div> -->
</div>
</template>
<script setup>
import SimpleMDE from "simplemde";
const propsData = defineProps({
detailPolicyItem: {
type: Object,
},
});
</script>
<style lang="scss" scoped>
.detail-title {
@include font(14px, rgba(42, 130, 228, 1));
border-bottom: 2px solid rgba(42, 130, 228, 1);
padding: 10px 0;
@include border-box;
}
.detail-policy {
padding: 10px 0;
@include border-box;

&-title {
@include font(14px, $color-black);
margin-bottom: 10px;
font-weight: bold;
}
&-content {
@include font(12px, $color-black);
line-height: 20px;
word-break: break-word;
}
}
</style>

+ 65
- 0
src/views/mobile/home/policy/OtherPolicy.vue View File

@@ -0,0 +1,65 @@
<template>
<div class="other-policy" v-if="dataList && dataList.length > 0">
<div class="other-policy-title">其他政策</div>
<div class="other-policy-content">
<div
v-for="item in dataList"
:key="item.releasePolicyId"
:title="item.policyName"
@click="goRoute(item)"
>
{{ item.policyName }}
</div>
</div>
</div>
</template>
<script setup>
import { onMounted } from "vue";
import { getReleasePolicyData } from "@/views/pc/policy/policy";
import { useRouter } from "vue-router";
const $router = useRouter();
const { params, getList } = getReleasePolicyData();
let dataList = ref([]);
const loading = ref(true);
const getListData = async () => {
let dataObj = await getList(params);
dataList.value = dataObj.list;
};
const goRoute = val => {
localStorage.setItem("policyInfo", JSON.stringify(val));
$router.push({
path: "/m_preferential-policy-detail",
query: {
releasePolicyId: val.releasePolicyId,
},
});
};
onMounted(async () => {
loading.value = true;
getListData();
loading.value = false;
});
</script>
<style lang="scss" scoped>
.other-policy {
// padding: 16px 42px;
@include border-box;
margin-bottom: 28px;

&-title {
@include font(16px, $color-black);
font-weight: bold;
margin-bottom: 20px;
}
&-content {
// @include flex(row, space-between, flex-start, wrap);
@include font(12px, $color-black);
line-height: 20px;
div {
margin-bottom: 8px;
@include text-ellipsis;
@include border-box;
}
}
}
</style>

+ 63
- 0
src/views/mobile/home/policy/PicturePolicy.vue View File

@@ -0,0 +1,63 @@
<template>
<div class="picture-box">
<div
class="picture-policy"
v-if="
propsData.policySupportKeyIndustries &&
propsData.policySupportKeyIndustries.length > 0
"
>
<div class="picture-policy-title">重点产业扶持政策</div>
<img
class="picture-policy-content"
:src="formatImg(propsData.policySupportKeyIndustries[0])"
alt="重点产业扶持政策"
/>
</div>
<div
class="picture-policy"
v-if="
propsData.domesticTalentIntroduction &&
propsData.domesticTalentIntroduction.length > 0
"
>
<div class="picture-policy-title">国内人才引进</div>
<img
class="picture-policy-content"
:src="formatImg(propsData.domesticTalentIntroduction[0])"
alt="国内人才引进"
/>
</div>
</div>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";
const propsData = defineProps({
policySupportKeyIndustries: {
type: Array,
},
domesticTalentIntroduction: {
type: Array,
},
});
</script>
<style lang="scss" scoped>
.picture-box {
// @include flex(row, space-between, flex-start, nowrap);
}
.picture-policy {
// padding: 10px 22px 0;
@include border-box;
margin-bottom: 10px;

&-title {
@include font(14px, $color-black);
text-align: center;
font-weight: bold;
margin-bottom: 10px;
}
&-content {
@include size(100%, 100%);
}
}
</style>

+ 44
- 0
src/views/mobile/home/policy/Policy.vue View File

@@ -0,0 +1,44 @@
<template>
<div
class="policy"
:style="{ color: propsData.policyItem.color, background: propsData.policyItem.bgColor }"
>
<div class="policy-title" :style="{ color: propsData.policyItem.color }">
{{ propsData.policyItem.title }}
</div>
<div
class="policy-content"
:style="{ color: propsData.policyItem.color }"
v-html="SimpleMDE.prototype.markdown(propsData.policyItem.content)"
></div>
</div>
</template>
<script setup>
import SimpleMDE from "simplemde";
const propsData = defineProps({
policyItem: {
type: Object,
},
});
</script>
<style lang="scss" scoped>
.policy {
background: rgba(255, 242, 197, 1);
border-radius: 20px;
padding: 10px 12px;
@include border-box;
margin-bottom: 12px;

&-title {
@include font(14px, $color-black);
text-align: center;
font-weight: bold;
margin-bottom: 10px;
}
&-content {
@include font(12px, $color-black);
line-height: 20px;
word-break: break-word;
}
}
</style>

+ 51
- 0
src/views/mobile/home/project/IndustrialCluster.vue View File

@@ -0,0 +1,51 @@
<template>
<div class="industrial-park">
<div class="industrial-park-title">产业聚集</div>
<div class="industrial-park-content">
<van-swipe class="my-swipe" :autoplay="5000" indicator-color="white">
<van-swipe-item
v-for="item in propsData.industrialAllocation"
:key="item.areaIndustrialAllocationId"
>
<Cluster :data="item" :height="200" />
</van-swipe-item>
</van-swipe>
</div>
</div>
</template>
<script setup>
import Cluster from "@/views/mobile/industry/Cluster.vue";
const propsData = defineProps({
industrialAllocation: {
type: Array,
},
});
</script>
<style lang="scss" scoped>
.industrial-park {
// padding: 16px 42px;
@include border-box;
margin-top: 10px;

&-title {
@include font(16px, $color-black);
font-weight: bold;
margin-bottom: 10px;
}
&-content {
@include flex(row, space-between, flex-start, wrap);
@include font(12px, $color-black);
line-height: 20px;
span {
margin-bottom: 10px;
@include text-ellipsis;
@include border-box;
}
}
}
:deep() {
// .van-swipe__indicator {
// color: rgba(204, 204, 204, 1);
// }
}
</style>

+ 51
- 0
src/views/mobile/home/project/IndustrialPark.vue View File

@@ -0,0 +1,51 @@
<template>
<div class="industrial-park">
<div class="industrial-park-title">产业园区</div>
<div class="industrial-park-content">
<!-- <van-swipe class="my-swipe" :autoplay="5000" indicator-color="white"> -->
<!-- <van-swipe-item v-for="item in 3"> -->
<Project :projectItem="propsData.projectItem" @doSth="showTipInfo" />
<!-- </van-swipce-item> -->
<!-- </van-swipe> -->
</div>
</div>
<TipDialog ref="tipRef"></TipDialog>
</template>
<script setup>
import TipDialog from "@/components/mobile/TipDialog.vue";

import Project from "@/views/mobile/project/Project.vue";
const propsData = defineProps({
projectItem: {
type: Object,
},
});
const tipRef = ref(null);
const showTipInfo = () => {
tipRef.value.showTip();
};
</script>

<style lang="scss" scoped>
.industrial-park {
// padding: 16px 42px;
@include border-box;
margin-top: 20px;

&-title {
@include font(16px, $color-black);
font-weight: bold;
margin-bottom: 10px;
}
&-content {
@include flex(row, space-between, flex-start, wrap);
@include font(12px, $color-black);
line-height: 20px;
span {
margin-bottom: 10px;
@include text-ellipsis;
@include border-box;
}
}
}
</style>

+ 92
- 0
src/views/mobile/home/project/Introduce.vue View File

@@ -0,0 +1,92 @@
<template>
<div class="introduce">
<div class="introduce-box">
<div class="introduce-box-title">区位优势</div>
<div>
{{ propsData.homeData.academyHistory }}
</div>
</div>
<img
class="introduce-img"
v-if="propsData.homeData.parkPicture && propsData.homeData.parkPicture.length > 0"
:src="formatImg(propsData.homeData.parkPicture[0])"
alt="园区图片"
/>
<div class="introduce-box">
<div class="introduce-box-title">镇域概括</div>
<ul class="introduce-ul">
<li>
<div class="subtitle">行政区域面积</div>
<div>
<span class="blue-text">{{ propsData.homeData.administrativeArea }}</span>
平方公里
</div>
</li>
<li>
<div>实有入口</div>
<div>
<span class="blue-text">{{ propsData.homeData.actualEntry }}</span>
</div>
</li>
<li>
<div>下辖</div>
<div>
<span class="blue-text">{{ propsData.homeData.haveJurisdictionOver }}</span>
个村
</div>
</li>
<li>
<div>居民区</div>
<div>
<span class="blue-text">{{ propsData.homeData.residentialArea }}</span>
</div>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";

const propsData = defineProps({
homeData: {
type: Object,
},
});
</script>
<style lang="scss" scoped>
.introduce {
padding-top: 5px;
@include border-box;
&-img {
@include size(100%, 325px);
border-radius: 20px;
@include border-box;
margin: 10px 0;
}
&-box {
@include font(12px, $color-black);
line-height: 20px;
&-title {
@include font(16px, $color-black);
margin-bottom: 10px;
}
}
}

.introduce-ul {
@include flex(row, space-between, flex-start, nowrap);
li {
// width: 25%;
// margin-top: 10px;
}
.subtitle {
@include font(12px, $color-black);
}
.blue-text {
@include font(20px, rgba(0, 153, 255, 1));
}
}
</style>

+ 70
- 0
src/views/mobile/industry/Cluster.vue View File

@@ -0,0 +1,70 @@
<template>
<div
:class="['cluster', propsData.height ? 'height_200' : '']"
:style="{ backgroundImage: 'url(' + formatImg(propsData.data.productUrl[0]) + ')' }"
>
<div class="cluster-title">{{ propsData.data.leadingIndustryName }}</div>
<div
class="cluster-content"
v-html="SimpleMDE.prototype.markdown(propsData.data.leadingIndustryDetail)"
></div>
</div>
</template>
<script setup>
import SimpleMDE from "simplemde";
import { formatImg } from "@/utils/common.js";

const propsData = defineProps({
data: {
type: Object,
},
height: {
type: Number,
},
});
</script>

<style lang="scss" scoped>
.cluster {
position: relative;
border-radius: 10px;
padding: 10px 20px;

@include border-box;
@include flex(column, flex-start, flex-start, nowrap);
margin-bottom: 10px;
// @include bg-image("https://img.js.design/assets/img/64abb2f1eec7205596b5791f.jpg");
background-size: 100% 100%;
background-repeat: no-repeat;
&-title {
@include font(14px, $color-white);
font-weight: bold;
margin-bottom: 10px;
z-index: 100;
}
&-content {
@include font(12px, $color-white);
line-height: 20px;
word-break: break-word;
z-index: 100;
}
}
.cluster:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 10px;

@include border-box;
}
.height_200 {
height: 200px;
.cluster-content {
@include text-ellipsis-multiple(7);
}
}
</style>

+ 67
- 0
src/views/mobile/industry/Enterprise.vue View File

@@ -0,0 +1,67 @@
<template>
<div class="enterprise_item_box">
<div class="block-title">{{ propsData.projectItem.element.text }}</div>
<div
class="enterprise"
v-for="item in propsData.projectItem.enterpriseData"
:key="item.enterpriseHomeLinkId"
>
<img :src="formatImg(item.url[0])" alt="图片" />
<div class="content" v-html="SimpleMDE.prototype.markdown(item.detail)"></div>
<!-- <div class="content">占地面积381亩</div>
<div class="content">建筑面积337515平方米</div> -->
<div class="consult" @click="showTipInfo">立 即 咨 询</div>
</div>
</div>
<TipDialog ref="tipRef"></TipDialog>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";
import SimpleMDE from "simplemde";
const propsData = defineProps({
projectItem: {
type: Object,
},
});

import TipDialog from "@/components/mobile/TipDialog.vue";
const tipRef = ref(null);
const showTipInfo = () => {
tipRef.value.showTip();
};
</script>
<style lang="scss" scoped>
.enterprise_item_box {
margin-bottom: 20px;
}
.block-title {
@include flex(row, center, center, nowrap);
@include font(14px, $color-black);
font-weight: 500;
}
.enterprise {
img {
@include font(12px, $color-black);
display: inline-block;
@include size(100%, 220px);
border-radius: 20px;
@include border-box;
margin: 12px 0;
}
.content {
@include font(12px, $color-black);
font-weight: 400;
margin-bottom: 12px;
line-height: 20px;
word-break: break-word;
}
.consult {
@include size(290px, 32px);
border-radius: 30px;
background: rgba(0, 153, 255, 1);
@include flex(row, center, center, nowrap);
@include font(14px, $color-white);
margin: 0 auto;
}
}
</style>

+ 95
- 0
src/views/mobile/industry/IndustryPark.vue View File

@@ -0,0 +1,95 @@
<template>
<div class="industry">
<div class="form-tab">
<span
v-for="item in tabArr"
:key="item.value"
:class="[currentTab == item.value ? 'active' : '']"
@click="changeTab(item.value)"
>
{{ item.label }}
</span>
</div>
<template v-if="!loading">
<template v-if="currentTab == 0">
<InvestmentVehicle
v-for="item in registrationPackageList"
:key="item.registrationPackageId"
:projectItem="item"
@doSth="showTipInfo"
></InvestmentVehicle>
</template>

<template v-if="currentTab == 1">
<Enterprise
v-for="item in enterpriseData"
:key="item.element.itemId"
:projectItem="item"
></Enterprise>
</template>
<template v-if="currentTab == 2">
<Cluster
v-for="item in industrialAllocation"
:key="item.areaIndustrialAllocationId"
:data="item"
></Cluster>
</template>
</template>
</div>
<TipDialog ref="tipRef"></TipDialog>
</template>
<script setup>
import Cluster from "./Cluster.vue";
import Enterprise from "./Enterprise.vue";
import InvestmentVehicle from "../project/Project.vue";
import TipDialog from "@/components/mobile/TipDialog.vue";

import { getIndustryData } from "@/views/pc/industry/getRegistrationPackageList";
import { onMounted } from "vue";
let {
loading,
tabArr,
currentTab,
changeTab,
registrationPackageList,
enterpriseData,
industrialAllocation,
} = getIndustryData();
onMounted(() => {
loading.value = true;
changeTab(0);
loading.value = false;
});
const tipRef = ref(null);
const showTipInfo = () => {
tipRef.value.showTip();
};
</script>
<style lang="scss" scoped>
.industry {
padding: 10px 15px;
overflow: hidden;
background: #fff;
@include border-box;
min-height: 100%;
}
.form-tab {
@include size(100%, auto);
margin: 10px auto 20px;
@include flex(row, space-between, center, nowrap);
span {
width: 30%;
height: 25px;
@include font(12px, $color-black);
font-weight: 500;
@include flex(row, center, center, nowrap);
border-radius: 20px;
background: rgba(245, 245, 245, 1);
@include border-box;
}
.active {
@include font(12px, $color-white);
background: rgba(0, 153, 255, 1);
}
}
</style>

+ 192
- 0
src/views/mobile/policy/PreferentialPolicy.vue View File

@@ -0,0 +1,192 @@
<template>
<div class="policy" v-if="!loading">
<div class="policy-search">
<div class="search-left" @click="show = true">
{{ selectText }}
<van-icon name="arrow-down" />
</div>
<van-field
class="search-input"
v-model="params.search"
@clear="toSearch"
@keyup.enter="toSearch"
/>
<div class="search-right">
<van-icon name="search" @click="toSearch" />
搜索
</div>
</div>
<div class="list_box">
<van-list
v-model:loading="pageLoading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
ref="checkbox"
>
<ul class="policy-ul">
<li
class="policy-item"
v-for="item in dataList"
:key="item.releasePolicyId"
@click="goRoute(item)"
>
<div class="policy-item-left">{{ item.policyName }}</div>
<div class="policy-item-right">
<span>政策类型:{{ item.policyType.text }}</span>
<span>发布时间:{{ formatDate(item.modifiedOn, "YYYY.MM.DD") }}</span>
</div>
</li>
</ul>
</van-list>
</div>
</div>
<van-popup v-model:show="show" round position="bottom">
<van-picker :columns="selectList" @confirm="changeSelect" @cancel="onCancel" />
</van-popup>
</template>
<script setup>
import { getReleasePolicyData } from "@/views/pc/policy/policy";
import { formatDate, sortByKey } from "@/utils/common.js";
import { onMounted } from "vue";
import useHomeStore from "@/store/module/home";
import { useRouter } from "vue-router";
const homeStore = useHomeStore();
const $router = useRouter();
const { params, getList, getPickList } = getReleasePolicyData();
let dataList = ref([]);
let selectList = ref([]);
let total = ref(0);
const loading = ref(true);
let selectText = ref("全部");
const pageLoading = ref(false);
const finished = ref(false);
const onCancel = () => {
show.value = false;
};
const onLoad = () => {
if (params.value.page !== 1) {
getListData();
}
};

const changeSelect = async ({ selectedOptions }) => {
selectText.value = selectedOptions[0].text;
console.log(selectedOptions[0]);
params.value.policyTypeId = selectedOptions[0].text == "全部" ? "" : selectedOptions[0].itemId;
params.value.page = 1;
let dataObj = await getList(params);
dataList.value = dataObj.list;
show.value = false;
};
const toSearch = async () => {
params.value.page = 1;
params.value.pageSize = 10;
let dataObj = await getList(params);
dataList.value = dataObj.list;
};

const goRoute = val => {
$router.push({
path: "/m_preferential-policy-detail",
query: {
releasePolicyId: val.releasePolicyId,
},
});
};
const getListData = async () => {
pageLoading.value = true;
let dataObj = await getList(params);
dataList.value = dataList.value.concat(dataObj.list);
total.value = dataObj.total;
params.value.page = dataObj.page_no;
pageLoading.value = false;
if (dataList.value.length >= total.value) {
finished.value = true;
} else {
finished.value = false;
}
params.value.page += 1;
};
onMounted(async () => {
loading.value = true;

let pickArr = await getPickList();
selectList.value = sortByKey(pickArr, "seq");
selectList.value.unshift({
text: "全部",
itemId: "000",
});
selectList.value.forEach(element => {
element.value = element.itemId;
});
// let dataObj = await getList(params);
// dataList.value = dataObj.list;
// total.value = dataObj.total;
getListData();
loading.value = false;
});

const input3 = ref("");
const show = ref(false);
</script>
<style lang="scss" scoped>
.policy {
height: calc(100vh - 48px - 150px);
padding: 20px 15px;
overflow: hidden;
background: rgba(245, 245, 245, 1);
@include border-box;
&-search {
@include size(100%, 34px);
border-radius: 25px;
border: 2px solid rgba(29, 112, 217, 1);
@include flex(row, space-between, center, nowrap);
@include font(12px, $color-black);
margin-bottom: 20px;
padding: 0 16px;
@include border-box;
.search-left {
color: rgba(29, 112, 217, 1);
}
.search-right {
color: rgba(153, 153, 153, 1);
}
.search-input {
flex: 1;
height: 100%;
background: transparent;
}
}
.list_box {
overflow-y: scroll;
height: calc(100vh - 48px - 150px - 84px);
// height: calc(100vh - 84px);
}
&-item {
@include size(100%, auto);
@include font(12px, $color-black);
background: rgba(255, 255, 255, 1);
border-radius: 20px;
padding: 10px 15px;
@include border-box;
margin-bottom: 15px;
&-left {
@include text-ellipsis-multiple(2);
font-weight: bold;
}
&-right {
height: 100%;
font-weight: normal;
@include flex(row, space-between, center, nowrap);
margin-top: 10px;
}
}
}
:deep() {
.van-cell__value,
.van-field__body {
height: 100%;
}
}
</style>

+ 70
- 0
src/views/mobile/policy/PreferentialPolicyDetail.vue View File

@@ -0,0 +1,70 @@
<template>
<section class="detail" v-if="!loading">
<div class="box">
<div class="title">{{ policyInfo.policyName }}</div>
<div class="subtitle">
<span>发布日期:{{ formatDate(policyInfo.modifiedOn, "YYYY年MM月DD日") }}</span>
<span>政策类型:{{ policyInfo.policyType.text }}</span>
<span>浏览量:{{ policyInfo.pageView || 0 }}</span>
</div>
<div class="content" v-html="formatHtml(policyInfo.policyText)"></div>
</div>
</section>
</template>
<script setup>
import { onMounted } from "vue";
import { getReleasePolicyData } from "@/views/pc/policy/policy";
import { formatDate } from "@/utils/common.js";
import { useRoute } from "vue-router";
import SimpleMDE from "simplemde";
const formatHtml = text => {
let html = SimpleMDE.prototype.markdown(text);
let imgUrl = `${import.meta.env.VITE_BASE_URL}/common/getImg?path=`;
html = html.replace(
/<img [^>]*src=['"]([^'"]+)[^>]*>/gi,
"<img src='" + imgUrl + "$1' width='100%'/>"
);
return html;
};
const { updatePageView, getReleasePolicyDetail } = getReleasePolicyData();
const loading = ref(true);
let policyInfo = ref({});
const $route = useRoute();
onMounted(async () => {
loading.value = true;
policyInfo.value = await getReleasePolicyDetail($route.query.releasePolicyId);
loading.value = false;
updatePageView($route.query.releasePolicyId);
});
</script>
<style lang="scss" scoped>
.detail {
min-height: calc(100vh - 48px - 150px);
padding: 10px 15px;
overflow: hidden;
background: rgba(245, 245, 245, 1);
@include border-box;
.box {
background: rgba(255, 255, 255, 1);
border-radius: 10px;
padding: 12px 0 30px 10px;
@include border-box;
}
.title {
@include font(14px, $color-black);
font-weight: 500;
}
.subtitle {
@include font(12px, rgba(97, 96, 96, 1));
margin: 20px 0;
span {
margin-right: 15px;
}
}
.content {
@include font(12px, $color-black);
line-height: 20px;
white-space: pre-wrap;
}
}
</style>

+ 62
- 0
src/views/mobile/project/InvestmentProjects.vue View File

@@ -0,0 +1,62 @@
<template>
<div class="container" v-if="!loading">
<van-tabs v-model:active="active" swipeable type="card" :ellipsis="false">
<van-tab
v-for="item in dataList"
:key="item.attractInvestmentProjectId"
:title="item.productName"
>
<Project :projectItem="item" @doSth="showTipInfo"></Project>
</van-tab>
</van-tabs>
</div>
<TipDialog ref="tipRef"></TipDialog>
</template>
<script setup>
import TipDialog from "@/components/mobile/TipDialog.vue";

import Project from "./Project.vue";
import { getReleasePolicyData } from "@/views/pc/project/project";
import { onMounted } from "vue";
const { getList } = getReleasePolicyData();
let dataList = ref([]);
const loading = ref(true);
onMounted(async () => {
loading.value = true;
dataList = await getList();
loading.value = false;
});
const active = ref(0);

const tipRef = ref(null);
const showTipInfo = () => {
tipRef.value.showTip();
};
</script>
<style lang="scss" scoped>
.container {
padding: 10px 15px;
overflow: hidden;
background: #fff;
@include border-box;
min-height: 100%;
:deep() {
.van-tabs__wrap {
margin-bottom: 20px;
}
.van-tabs__nav--card {
border: none;
margin: 0;
}
.van-tab--card {
border-right: none;
border-radius: 25px;
// margin-right: 8px;
color: rgba(0, 0, 0, 1);
}
.van-tab--card.van-tab--active {
color: #fff;
}
}
}
</style>

+ 60
- 0
src/views/mobile/project/Project.vue View File

@@ -0,0 +1,60 @@
<template>
<div class="project">
<img :src="formatImg(propsData.projectItem.productUrl[0])" alt="招商项目图片" />

<div class="project-title">{{ propsData.projectItem.productName }}</div>
<div class="project-content">
{{ propsData.projectItem.productText }}
</div>
<span class="project-consult" @click="doSth">立 即 咨 询</span>
</div>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";
const propsData = defineProps({
projectItem: {
type: Object,
},
});
const emit = defineEmits(["doSth"]);
const doSth = () => {
emit("doSth");
};
</script>
<style lang="scss" scoped>
.project {
// background: rgba(255, 255, 255, 1);
// padding: 24px 12px;
@include border-box;
@include flex(column, flex-start, center, nowrap);
margin-bottom: 24px;

img {
@include font(12px, $color-black);
@include size(100%, 238px);
border-radius: 20px;
margin-bottom: 10px;
@include border-box;
}

&-title {
width: 100%;
@include font(14px, $color-black);
font-weight: bold;
}
&-content {
@include font(12px, $color-black);
line-height: 20px;
margin: 10px 0;
word-break: break-word;
white-space: pre-wrap;
}
&-consult {
@include size(290px, 32px);
border-radius: 30px;
background: rgba(0, 153, 255, 1);
@include flex(row, center, center, nowrap);
@include font(14px, $color-white);
}
}
</style>

+ 49
- 0
src/views/pc/aboutMe/About.vue View File

@@ -0,0 +1,49 @@
<template>
<div class="block">
<div class="title">{{ propsData.aboutItem.label }}</div>
<div class="content" v-html="formatHtml(propsData.aboutItem.value)"></div>
</div>
</template>
<script setup>
import SimpleMDE from "simplemde";

const propsData = defineProps({
aboutItem: {
type: Object,
},
});

const formatHtml = text => {
let html = SimpleMDE.prototype.markdown(text);
let imgUrl = `${import.meta.env.VITE_BASE_URL}/common/getImg?path=`;
html = html.replace(
/<img [^>]*src=['"]([^'"]+)[^>]*>/gi,
"<img src='" + imgUrl + "$1' width='100%'/>"
);
return html;
};
</script>
<style lang="scss" scoped>
.block {
background: rgba(255, 255, 255, 1);
border-radius: 10px;
padding: 36px;
@include border-box;
@include flex(column, space-between, center, nowrap);
margin-bottom: 24px;
.title {
@include font(36px, $color-black);
font-weight: bold;
margin-bottom: 50px;
}
.content {
@include font(20px, $color-black);
line-height: 30px;
word-break: break-word;
}
img {
@include size(100%, auto);
margin-top: 20px;
}
}
</style>

+ 137
- 0
src/views/pc/aboutMe/AboutMe.vue View File

@@ -0,0 +1,137 @@
<template>
<div class="about">
<div class="about-box">
<van-sticky>
<div class="form-tab">
<span
v-for="item in tabArr"
:key="item.value"
:class="[currentTab == item.value ? 'active' : '']"
@click="changeTab(item.value)"
>
{{ item.label }}
</span>
</div>
</van-sticky>

<template v-if="!loading">
<About
:id="'tab' + index"
class="scroll-item"
v-for="(item, index) in tabArr"
:key="item.value"
:aboutItem="{
label: item.label,
value: homeData[tab[index].key],
}"
></About>
</template>
</div>
</div>
</template>
<script setup>
import { onMounted, onUnmounted, ref } from "vue";
import About from "./About.vue";
import useHomeStore from "@/store/module/home";
import VueScrollTo from "vue-scrollto";
import { debounce } from "@/utils/common.js";

const homeStore = useHomeStore();
let homeData = ref({});

const tab = [
{
label: "区位优势",
value: 0,
key: "regionalAdvantages",
},
{
label: "发展机遇",
value: 1,
key: "developmentOpportunity",
},
{
label: "配套建设",
value: 2,
key: "supportingConstruction",
},
{
label: "发展趋势",
value: 3,
key: "developmentTendency",
},
{
label: "交通优势",
value: 4,
key: "transportationAdvantage",
},
{
label: "镇域概况",
value: 5,
key: "townProfile",
},
{
label: "书院介绍",
value: 6,
key: "academyHistory",
},
];
const tabArr = ref(tab);
const currentTab = ref(0);
const loading = ref(true);
const changeTab = val => {
currentTab.value = val;
VueScrollTo.scrollTo(`#tab${val}`, 500, { offset: -132 });
};

const scrollListenerHandler = debounce(() => {
const scrollTop =
window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; // 滚动条偏移量
document.querySelectorAll(".scroll-item").forEach((item, index) => {
if (item.offsetTop - 132 <= scrollTop) {
currentTab.value = index;
}
});
}, 100);
onMounted(() => {
loading.value = true;
homeData.value = homeStore.homeData.value;
loading.value = false;
window.addEventListener("scroll", scrollListenerHandler);
});
onUnmounted(() => {
window.removeEventListener("scroll", scrollListenerHandler);
});
</script>
<style lang="scss" scoped>
.about {
background: rgba(245, 245, 245, 1);
&-box {
@include size($contentWidth, auto);
margin: 0 auto;
padding: 20px 0 58px;
@include border-box;
}
.form-tab {
background: rgba(245, 245, 245, 1);
@include size($contentWidth, auto);
padding: 25px 0;
@include flex(row, space-between, center, nowrap);
@include border-box;
span {
width: 200px;
height: 82px;
@include font(24px, $color-black);
font-weight: 500;
@include flex(row, center, center, nowrap);
border-radius: 20px;
cursor: pointer;
@include border-box;
}
.active {
@include font(24px, $color-white);
background: rgba(0, 153, 255, 1);
}
}
}
</style>

+ 215
- 0
src/views/pc/apply/ApplyForEntry.vue View File

@@ -0,0 +1,215 @@
<template>
<section class="apply-box">
<div class="form-tab">
<span
v-for="(item, index) in tabArr"
:class="[currentTb == index ? 'active' : '']"
@click="changeTab(item.value, index)"
:key="item.value"
>
{{ item.label }}
</span>
</div>
<div class="form">
<el-form
v-if="currentTb == 0"
:label-position="labelPosition"
label-width="170px"
:model="params"
style="max-width: 800px"
>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
企业名称
</div>
</template>
<el-input
class="form-item-content"
v-model="params.companyName"
placeholder="请输入企业名称"
maxlength="50"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
联系人
</div>
</template>
<el-input
class="form-item-content"
v-model="params.name"
placeholder="请输入联系人名称"
maxlength="10"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
联系电话
</div>
</template>
<el-input
class="form-item-content"
v-model="params.mobile"
placeholder="请输入联系电话"
maxlength="11"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">预计产税</div>
</template>
<el-input
class="form-item-content"
v-model="params.estimatedPropertyTax"
placeholder="请输入预计产税"
maxlength="20"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label><div class="form-item-label">需求内容</div></template>
<el-input
class="form-item-content"
v-model="params.text"
type="textarea"
rows="10"
placeholder="请输入需求内容"
resize="none"
maxlength="200"
/>
</el-form-item>
</el-form>

<el-form
v-else
:label-position="labelPosition"
label-width="170px"
:model="params"
style="max-width: 800px"
>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
企业名称
</div>
</template>
<el-input
class="form-item-content"
v-model="params.companyName"
placeholder="请输入企业名称"
maxlength="50"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
联系人
</div>
</template>
<el-input
class="form-item-content"
v-model="params.name"
placeholder="请输入联系人名称"
maxlength="10"
/>
</el-form-item>
<el-form-item class="form-item">
<template v-slot:label>
<div class="form-item-label">
<span class="required">*</span>
联系电话
</div>
</template>
<el-input
class="form-item-content"
v-model="params.mobile"
placeholder="请输入联系电话"
maxlength="11"
/>
</el-form-item>
</el-form>
<el-button class="submit" round type="primary" @click="submitForm">提交申请</el-button>
<div class="submit-tip">提交后我们将在1-7个工作日内联系您</div>
</div>
</section>
</template>
<script setup>
import { apply } from "@/views/pc/apply/apply";
const { labelPosition, currentTb, tabArr, params, applyForEntry, changeTab, submitForm } = apply();
</script>
<style lang="scss" scoped>
.apply-box {
overflow: hidden;
background: url(https://img.js.design/assets/img/64acb3a790162375e01752b3.jpg) no-repeat;
background-size: 100% 100%;
}
.form-tab {
@include size($contentWidth, auto);
margin: 25px auto;
@include flex(row, space-between, center, nowrap);
span {
width: 50%;
height: 82px;
@include font(24px, $color-black);
font-weight: 500;
@include flex(row, center, center, nowrap);
border-radius: 20px;
background: rgba(245, 245, 245, 1);
cursor: pointer;
@include border-box;
}
.active {
@include font(24px, $color-white);
background: rgba(0, 153, 255, 1);
}
}
.form {
@include size($contentWidth, auto);
margin: 0 auto 60px;
padding: 45px 220px 30px 178px;
background: rgba(255, 255, 255, 1);
border-radius: 20px;
@include border-box;
text-align: center;

&-item {
border-radius: 50px;
background: rgba(242, 244, 249, 1);
padding: 10px 25px 10px 0;
@include border-box;
&-label {
@include font(20px, $color-black);
border-right: 3px solid rgba(145, 145, 145, 1);
padding-right: 25px;
@include border-box;
}
}
.submit {
@include size(240px, 68px);
@include font(24px, $color-white);
border-radius: 34px;
@include border-box;
margin: 100px 0 52px;
}
.submit-tip {
@include font(20px, $color-black);
}
}
.required {
color: rgba(255, 0, 0, 1);
}
:deep() {
.el-input__wrapper,
.el-textarea__inner {
background: none;
box-shadow: none;
}
}
</style>

+ 74
- 0
src/views/pc/apply/apply.js View File

@@ -0,0 +1,74 @@
import { addEnter } from "@/apis/index";
import { ref } from "vue";
import { toast, throttle } from "@/utils/common.js";

export function apply() {
// tab相关
const currentTb = ref(0);
const tabArr = ref([{
label: "我是企业,我要申请入驻",
value: "012-01894315f4680185",
},
{
label: "我是中介,我要合作加入",
value: "012-01894315f4690186",
},
]);
const changeTab = (val, index) => {
params.value = {
enterId: "",
mobile: "",
name: "",
companyName: "",
type: {
text: "012-01894315f4680185", //企业入驻 012-01894315f4680185 中介加入 012-01894315f4690186
},
estimatedPropertyTax: "",
};
params.value.type.text = val;
currentTb.value = index;
};

// 表单相关
const labelPosition = ref("right");
const params = ref({
enterId: "",
mobile: "",
name: "",
companyName: "",
type: {
text: "012-01894315f4680185", //企业入驻 012-01894315f4680185 中介加入 012-01894315f4690186
},
estimatedPropertyTax: "",
});

const applyForEntry = async() => {
let data;
let res = await addEnter(params);
if (res.data.status == 0) {
data = res.data.data;
}
return data;
};
const submitForm = throttle(async() => {
let { companyName, name, mobile } = params.value;
if (!companyName.trim()) {
toast("请输入企业名称!", "error");
return;
}
if (!name.trim()) {
toast("请输入联系人名称!", "error");
return;
}
if (!mobile.trim()) {
toast("请输入联系电话!", "error");
return;
}
let res = await addEnter(params.value);
if (res.data.status == 0) {
toast("提交成功!", "success");
}
}, 500);

return { labelPosition, currentTb, tabArr, params, applyForEntry, changeTab, submitForm };
}

+ 68
- 0
src/views/pc/home/Home.vue View File

@@ -0,0 +1,68 @@
<template>
<div v-if="!loading">
<div class="wrap-box">
<Introduce :homeData="homeData"></Introduce>
</div>
<div class="project-container">
<div class="wrap-box"><Project></Project></div>
</div>
<IndustrialCluster :industrialAllocation="industrialAllocation"></IndustrialCluster>

<div class="wrap-box">
<Policy v-for="(item, index) in policy" :key="index" :policyItem="item"></Policy>
<PicturePolicy
:policySupportKeyIndustries="homeData.policySupportKeyIndustries"
:domesticTalentIntroduction="homeData.domesticTalentIntroduction"
></PicturePolicy>
<template v-if="detailPolicy.length > 0">
<DetailPolicy
v-for="(item, index) in detailPolicy"
:key="index"
:detailPolicyItem="item"
></DetailPolicy>
</template>

<OtherPolicy></OtherPolicy>
</div>
</div>
</template>
<script setup>
import Introduce from "./project/Introduce.vue";
import Project from "./project/Project.vue";
import IndustrialCluster from "./project/IndustrialCluster.vue";
import Policy from "./policy/Policy.vue";
import PicturePolicy from "./policy/PicturePolicy.vue";
import DetailPolicy from "./policy/DetailPolicy.vue";
import OtherPolicy from "./policy/OtherPolicy.vue";
import { getHomeData } from "@/views/pc/home/home";
import { onMounted } from "vue";
const { getAreaIndustrialAllocation, setDetailPolicy, setPolicy, updatePageView } = getHomeData();
let homeData = ref({});
let industrialAllocation = ref([]);
let policy = ref([]);
let detailPolicy = ref([]);
const loading = ref(true);
import useHomeStore from "@/store/module/home";
const homeStore = useHomeStore();
onMounted(async () => {
loading.value = true;
homeData.value = homeStore.homeData.value;
policy = setPolicy(homeData.value);
detailPolicy = setDetailPolicy(homeData.value);
industrialAllocation.value = await getAreaIndustrialAllocation();
loading.value = false;
updatePageView(homeData.value.areaInfoManagementId);
});
</script>

<style lang="scss" scoped>
.project-container {
background: rgba(248, 250, 253, 1);
padding: 12px 0;
@include border-box;
}
.wrap-box {
@include size($contentWidth, auto);
margin: 0 auto;
}
</style>

+ 130
- 0
src/views/pc/home/home.js View File

@@ -0,0 +1,130 @@
import {
getAreaData,
getAreaIndustrialAllocationList,
updateAreaInfoManagementPageView,
getRegistrationPackageByFirst,
} from "@/apis/index";
import { toast } from "@/utils/common.js";

export function getHomeData() {
// 获取数据
const getData = async() => {
let data;
try {
let res = await getAreaData();
if (res.data.status == 0) {
data = res.data.data;
} else {
toast(res.data.msg, "error");
}
} catch (error) {
toast("获取数据失败,请刷新重试!", "error");
}
return data;
};

// 获取主导产业
const getAreaIndustrialAllocation = async() => {
let data;
try {
let res = await getAreaIndustrialAllocationList();
if (res.data.status == 0) {
data = res.data.data;
} else {
toast(res.data.msg, "error");
}
} catch (error) {
toast("获取主导产业失败,请刷新重试!", "error");
}
return data;
};

// 获取最近那一个编辑或者上架的招商载体
const getRegistrationPackage = async() => {
let data;
let res = await getRegistrationPackageByFirst();
if (res.data.status == 0) {
data = res.data.data;
}
return data;
};

const setDetailPolicy = homeData => {
const detailPolicy = ref([]);
if (homeData) {
const {
policyTitle,
policyText,
policySubtitleI,
policyContentI,
policySubtitleII,
policyContentII,
} = homeData;
detailPolicy.value = [{
title: policyTitle,
content: policyText,
},
{
title: policySubtitleI,
content: policyContentI,
},
{
title: policySubtitleII,
content: policyContentII,
},
];
}
return detailPolicy;
};

const setPolicy = homeData => {
const policy = ref([]);
if (homeData) {
const { fiscalTaxationPolicy, talentRelatedPolicy, industryRelatedPolicy } = homeData;
policy.value = [{
title: "财税政策",
content: fiscalTaxationPolicy,
color: "#333",
bgColor: "rgb(255, 242, 197)",
},
{
title: "人才相关政策",
content: talentRelatedPolicy,
color: "#333",
bgColor: "rgba(172, 217, 255, 1)",
},
{
title: "产业相关政策",
content: industryRelatedPolicy,
color: "#fff",
bgColor: "rgba(28, 54, 127, 1)",
},
];
}

return policy;
};

const updatePageView = async p => {
let data;
try {
let res = await updateAreaInfoManagementPageView(p);
if (res.data.status == 0) {
data = res.data.data;
} else {
toast(res.data.msg, "error");
}
} catch (error) {
console.log(error);
}
return data;
};
return {
getData,
getAreaIndustrialAllocation,
setDetailPolicy,
setPolicy,
updatePageView,
getRegistrationPackage,
};
}

+ 45
- 0
src/views/pc/home/policy/DetailPolicy.vue View File

@@ -0,0 +1,45 @@
<template>
<div class="detail-title">{{ propsData.detailPolicyItem.title }}</div>
<div class="detail-policy">
<div
class="detail-policy-content"
v-html="SimpleMDE.prototype.markdown(propsData.detailPolicyItem.content)"
></div>
<!-- <div class="detail-policy-content">
按当年销售金额最高 10% 给予奖励,单款芯片产品年度奖励总额不超过 500 万元。
</div> -->
</div>
</template>
<script setup>
// 安装 "simplemde": "^1.11.2",
// 安装 "codemirror": "^5.65.14",
// 安装 "marked": "^3.0.8",
import SimpleMDE from "simplemde";
const propsData = defineProps({
detailPolicyItem: {
type: Object,
},
});
</script>
<style lang="scss" scoped>
.detail-title {
@include font(24px, rgba(42, 130, 228, 1));
border-bottom: 3px solid rgba(42, 130, 228, 1);
padding: 10px 0 18px;
@include border-box;
}
.detail-policy {
padding: 20px 0 14px;
@include border-box;

&-title {
@include font(20px, $color-black);
margin-bottom: 10px;
}
&-content {
@include font(18px, $color-black);
line-height: 30px;
word-break: break-word;
}
}
</style>

+ 68
- 0
src/views/pc/home/policy/OtherPolicy.vue View File

@@ -0,0 +1,68 @@
<template>
<div class="other-policy" v-if="dataList && dataList.length > 0">
<div class="other-policy-title">其他政策</div>
<div class="other-policy-content">
<span
v-for="item in dataList"
:key="item.releasePolicyId"
:title="item.policyName"
@click="goRoute(item)"
>
{{ item.policyName }}
</span>
</div>
</div>
</template>
<script setup>
import { onMounted } from "vue";
import { getReleasePolicyData } from "@/views/pc/policy/policy";
import { routerOpenInNewWindow } from "@/utils/common.js";
const { params, getList } = getReleasePolicyData();
let dataList = ref([]);
const loading = ref(true);
const getListData = async () => {
let dataObj = await getList(params);
dataList.value = dataObj.list;
};
const goRoute = val => {
localStorage.setItem("policyInfo", JSON.stringify(val));
routerOpenInNewWindow({
path: "/p_preferential-policy-detail",
query: {
releasePolicyId: val.releasePolicyId,
},
});
};
onMounted(async () => {
loading.value = true;
getListData();
loading.value = false;
});
</script>
<style lang="scss" scoped>
.other-policy {
padding: 16px;
@include border-box;
margin-bottom: 28px;

&-title {
@include font(24px, $color-black);
text-align: center;
font-weight: bold;
margin-bottom: 20px;
}
&-content {
@include flex(row, space-between, flex-start, wrap);
@include font(18px, $color-black);
line-height: 20px;
span {
width: 50%;
margin-bottom: 20px;
@include text-ellipsis;
padding-right: 30px;
@include border-box;
cursor: pointer;
}
}
}
</style>

+ 76
- 0
src/views/pc/home/policy/PicturePolicy.vue View File

@@ -0,0 +1,76 @@
<template>
<div
:class="[
'picture-box',
propsData.policySupportKeyIndustries &&
propsData.policySupportKeyIndustries.length > 0 &&
propsData.domesticTalentIntroduction &&
propsData.domesticTalentIntroduction.length > 0
? ''
: 'box-center',
]"
>
<div
class="picture-policy"
v-if="
propsData.policySupportKeyIndustries &&
propsData.policySupportKeyIndustries.length > 0
"
>
<div class="picture-policy-title">重点产业扶持政策</div>
<img
class="picture-policy-content"
:src="formatImg(propsData.policySupportKeyIndustries[0])"
alt="重点产业扶持政策"
/>
</div>
<div
class="picture-policy"
v-if="
propsData.domesticTalentIntroduction &&
propsData.domesticTalentIntroduction.length > 0
"
>
<div class="picture-policy-title">国内人才引进</div>
<img
class="picture-policy-content"
:src="formatImg(propsData.domesticTalentIntroduction[0])"
alt="国内人才引进"
/>
</div>
</div>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";
const propsData = defineProps({
policySupportKeyIndustries: {
type: Array,
},
domesticTalentIntroduction: {
type: Array,
},
});
</script>
<style lang="scss" scoped>
.picture-box {
@include flex(row, space-between, flex-start, nowrap);
}
.box-center {
justify-content: center;
}
.picture-policy {
padding: 10px 22px 0;
@include border-box;
margin-bottom: 24px;

&-title {
@include font(20px, $color-black);
text-align: center;
font-weight: bold;
margin-bottom: 10px;
}
&-content {
@include size(100%, 100%);
}
}
</style>

+ 47
- 0
src/views/pc/home/policy/Policy.vue View File

@@ -0,0 +1,47 @@
<template>
<div
class="policy"
:style="{ color: propsData.policyItem.color, background: propsData.policyItem.bgColor }"
>
<div class="policy-title" :style="{ color: propsData.policyItem.color }">
{{ propsData.policyItem.title }}
</div>
<div
class="policy-content"
:style="{ color: propsData.policyItem.color }"
v-html="SimpleMDE.prototype.markdown(propsData.policyItem.content)"
></div>
</div>
</template>
<script setup>
// 安装 "simplemde": "^1.11.2",
// 安装 "codemirror": "^5.65.14",
// 安装 "marked": "^3.0.8",
import SimpleMDE from "simplemde";
const propsData = defineProps({
policyItem: {
type: Object,
},
});
</script>
<style lang="scss" scoped>
.policy {
background: rgba(255, 242, 197, 1);
border-radius: 20px;
padding: 16px 12px;
@include border-box;
margin-bottom: 24px;

&-title {
font-size: 20px;
text-align: center;
font-weight: bold;
margin-bottom: 20px;
}
&-content {
font-size: 18px;
line-height: 30px;
word-break: break-word;
}
}
</style>

+ 40
- 0
src/views/pc/home/project/IndustrialCluster.vue View File

@@ -0,0 +1,40 @@
<template>
<div class="industrial-park">
<el-carousel :interval="4000" type="card" indicator-position="none">
<el-carousel-item
v-for="item in propsData.industrialAllocation"
:key="item.areaIndustrialAllocationId"
>
<!-- <h3 text="2xl" justify="center">{{ item }}</h3> -->
<Cluster :data="item"></Cluster>
</el-carousel-item>
</el-carousel>
</div>
</template>
<script setup>
import Cluster from "@/views/pc/industry/Cluster.vue";
const propsData = defineProps({
industrialAllocation: {
type: Array,
},
});
</script>
<style lang="scss" scoped>
.industrial-park {
padding: 46px 0;
@include border-box;
margin-top: 10px;
}
:deep() {
.el-carousel__container {
height: auto;
min-height: 400px;
}
.el-carousel__item {
border-radius: 10px;
}
// .van-swipe__indicator {
// color: rgba(204, 204, 204, 1);
// }
}
</style>

+ 104
- 0
src/views/pc/home/project/Introduce.vue View File

@@ -0,0 +1,104 @@
<template>
<div class="introduce">
<div class="info">
<div class="info-box">
<div class="info-box-title">区位优势</div>
<div>
{{ propsData.homeData.academyHistory }}
</div>
</div>
<div class="info-box">
<div class="info-box-title margin-title">镇域概括</div>
<ul class="info-ul">
<li>
<div class="subtitle">行政区域面积</div>
<div>
<span class="blue-text">
{{ propsData.homeData.administrativeArea }}
</span>
平方公里
</div>
</li>
<li>
<div>实有入口</div>
<div>
<span class="blue-text">{{ propsData.homeData.actualEntry }}</span>
</div>
</li>
<li>
<div>下辖</div>
<div>
<span class="blue-text">
{{ propsData.homeData.haveJurisdictionOver }}
</span>
个村
</div>
</li>
<li>
<div>居民区</div>
<div>
<span class="blue-text">{{ propsData.homeData.residentialArea }}</span>
</div>
</li>
</ul>
</div>
</div>
<img
v-if="propsData.homeData.parkPicture && propsData.homeData.parkPicture.length > 0"
class="introduce-img"
:src="formatImg(propsData.homeData.parkPicture[0])"
alt="园区图片"
/>
</div>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";

const propsData = defineProps({
homeData: {
type: Object,
},
});
</script>
<style lang="scss" scoped>
.introduce {
margin: 56px auto;
@include flex(row, space-between, stretch, nowrap);

&-img {
@include size(550px, auto);
border-radius: 20px;
@include border-box;
}
}
.info {
flex: 1;
padding-right: 52px;
@include border-box;
&-box {
@include font(20px, $color-black);
&-title {
@include font(24px, $color-black);
margin-bottom: 36px;
}
}
}
.margin-title {
margin-top: 48px;
}
.info-ul {
@include flex(row, space-between, flex-start, wrap);
li {
width: 50%;
margin-top: 24px;
}
.subtitle {
@include font(20px, $color-black);
}
.blue-text {
@include font(48px, rgba(0, 153, 255, 1));
}
}
</style>

+ 79
- 0
src/views/pc/home/project/Project.vue View File

@@ -0,0 +1,79 @@
<template>
<div class="project">
<ul class="project-left">
<li class="active">招商载体</li>
<li @click="toPath('/p_industry-park', 1)">企业链家</li>
<li @click="toPath('/p_apply-for-entry')">
<div>
我是企业
<br />
我要入驻
</div>
<el-icon size="30px"><Right /></el-icon>
</li>
</ul>
<div class="project-right" v-if="!loading">
<ProjectItem :projectItem="registrationPackage"></ProjectItem>
</div>
</div>
</template>
<script setup>
import { Right } from "@element-plus/icons-vue"; // 引入 Edit 这个 svg组件
import ProjectItem from "./ProjectItem.vue";
import { getHomeData } from "../home";
import { onBeforeMount } from "vue";
import { useRoute, useRouter } from "vue-router"; //1.引入路由
const $router = useRouter(); //2.实例化路由
const { getRegistrationPackage } = getHomeData();
let registrationPackage = ref({});
const loading = ref(true);
onBeforeMount(async () => {
loading.value = true;
registrationPackage.value = await getRegistrationPackage();
loading.value = false;
});

const toPath = (path, tab) => {
$router.push({
path,
query: {
tab,
},
});
};
</script>
<style lang="scss" scoped>
.project {
@include flex(row, space-between, flex-start, nowrap);

&-left {
width: 282px;
li {
@include size(100%, 130px);
@include font(24px, $color-black);
font-weight: 500;
background: linear-gradient(
90deg,
rgba(0, 134, 231, 0.2) 0%,
rgba(0, 136, 230, 0) 100%
);
margin-bottom: 5px;
border-radius: 20px;
@include flex(row, center, center, nowrap);
@include border-box;
cursor: pointer;
}
.active {
background: linear-gradient(90deg, rgba(0, 134, 231, 1) 0%, rgba(0, 136, 230, 0) 100%);
@include font(24px, $color-white);
}
}
&-right {
flex: 1;
}

.el-icon {
margin-left: 20px;
}
}
</style>

+ 75
- 0
src/views/pc/home/project/ProjectItem.vue View File

@@ -0,0 +1,75 @@
<template>
<div class="project">
<img :src="formatImg(propsData.projectItem.productUrl[0])" alt="招商项目图片" />
<div class="project-right">
<div class="content">
<span class="content-title">{{ propsData.projectItem.productName }}</span>
<div class="content-content" :title="propsData.projectItem.productText">
{{ propsData.projectItem.productText }}
</div>
</div>
<span class="project-right-consult" @click="showTipInfo">立 即 咨 询</span>
</div>
</div>
<TipDialog ref="tipRefPC"></TipDialog>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";
const propsData = defineProps({
projectItem: {
type: Object,
},
});

import TipDialog from "@/components/pc/TipDialogPC.vue";
const tipRefPC = ref(null);
const showTipInfo = () => {
tipRefPC.value.showTip();
};
</script>
<style lang="scss" scoped>
.project {
background: rgba(255, 255, 255, 1);
border-radius: 10px;
padding: 24px 20px;
@include border-box;
@include flex(row, space-between, center, nowrap);

img {
@include size(496px, 352px);
margin-right: 25px;
border-radius: 20px;
@include border-box;
}
&-right {
flex: 1;
@include size(auto, 352px);
@include flex(column, space-between, flex-start, nowrap);
.content {
flex: 1;
padding-bottom: 20px;
@include flex(column, flex-start, flex-start, nowrap);
&-title {
@include font(24px, $color-black);
font-weight: 400;
margin-bottom: 10px;
}
&-content {
@include font(18px, $color-black);
line-height: 26px;
@include text-ellipsis-multiple(10);
}
}

&-consult {
@include size(242px, 42px);
margin: 0 auto;
border-radius: 50px;
background: rgba(0, 153, 255, 1);
@include flex(row, center, center, nowrap);
@include font(20px, $color-white);
cursor: pointer;
}
}
}
</style>

+ 65
- 0
src/views/pc/industry/Cluster.vue View File

@@ -0,0 +1,65 @@
<template>
<div
class="cluster"
:style="{
backgroundImage: 'url(' + formatImg(propsData.data.productUrl[0]) + ')',
minHeight: $route.path === '/p_industry-park' ? auto : '500px',
}"
>
<div class="cluster-title">{{ propsData.data.leadingIndustryName }}</div>
<div
class="cluster-content"
v-html="SimpleMDE.prototype.markdown(propsData.data.leadingIndustryDetail)"
></div>
</div>
</template>
<script setup>
import SimpleMDE from "simplemde";
import { formatImg } from "@/utils/common.js";
import { useRoute } from "vue-router"; //1.引入路由
const $route = useRoute(); //2.实例化路由
const propsData = defineProps({
data: {
type: Object,
},
});
</script>
<style lang="scss" scoped>
.cluster {
position: relative;
background: rgba(255, 255, 255, 1);
border-radius: 10px;
padding: 35px 40px 35px 32px;
@include border-box;
@include flex(column, flex-start, flex-start, nowrap);
margin-bottom: 24px;
// @include bg-image("https://img.js.design/assets/img/64abb2f1eec7205596b5791f.jpg");
background-size: 100% 100%;
background-repeat: no-repeat;
&-title {
@include font(36px, $color-white);
font-weight: bold;
margin-bottom: 20px;
z-index: 100;
}
&-content {
@include font(20px, $color-white);
line-height: 30px;
@include text-ellipsis-multiple(8);
word-break: break-word;
z-index: 100;
}
}

.cluster:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 10px;
background-color: rgba(0, 0, 0, 0.5);
@include border-box;
}
</style>

+ 100
- 0
src/views/pc/industry/Enterprise.vue View File

@@ -0,0 +1,100 @@
<template>
<div class="enterprise_item_box">
<div class="block-title">
<span>{{ propsData.projectItem.element.text }}</span>
</div>
<div class="enterprise_item">
<div
class="enterprise"
v-for="item in propsData.projectItem.enterpriseData"
:key="item.enterpriseHomeLinkId"
>
<img :src="formatImg(item.url[0])" alt="图片" />
<div class="enterprise-right">
<div class="content">
<div
class="content-title"
v-html="SimpleMDE.prototype.markdown(item.detail)"
></div>
</div>
<span class="enterprise-right-consult" @click="showTipInfo">立 即 咨 询</span>
</div>
</div>
</div>
</div>
<TipDialog ref="tipRefPC"></TipDialog>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";
import SimpleMDE from "simplemde";
const propsData = defineProps({
projectItem: {
type: Object,
},
});

import TipDialog from "@/components/pc/TipDialogPC.vue";
const tipRefPC = ref(null);
const showTipInfo = () => {
tipRefPC.value.showTip();
};
</script>
<style lang="scss" scoped>
.enterprise_item_box {
margin-bottom: 40px;
}
.block-title {
@include flex(row, center, center, nowrap);

span {
@include font(24px, $color-black);
font-weight: 500;
border-top-left-radius: 50px;
border-top-right-radius: 50px;
background: rgba(255, 255, 255, 1);
padding: 10px 70px;
max-width: $contentWidth;
}
}
.enterprise_item {
background: rgba(255, 255, 255, 1);
border-radius: 20px;
@include border-box;
}
.enterprise {
padding: 24px 12px;
@include border-box;
@include flex(row, space-between, center, nowrap);
img {
@include size(766px, 460px);
margin-right: 32px;
}
&-right {
flex: 1;
@include size(auto, 460px);
@include flex(column, space-between, flex-start, nowrap);
.content {
flex: 1;
padding-bottom: 20px;
@include flex(column, flex-start, flex-start, nowrap);
&-title {
// p {
@include font(20px, $color-black);
font-weight: 400;
margin-bottom: 25px;
word-break: break-word;
// }
}
}

&-consult {
@include size(100%, 50px);
border-radius: 50px;
background: rgba(0, 153, 255, 1);
@include flex(row, center, center, nowrap);
@include font(20px, $color-white);
cursor: pointer;
}
}
}
</style>

+ 100
- 0
src/views/pc/industry/IndustryPark.vue View File

@@ -0,0 +1,100 @@
<template>
<div class="industry">
<div class="box">
<div class="form-tab">
<span
v-for="item in tabArr"
:key="item.value"
:class="[currentTab == item.value ? 'active' : '']"
@click="changeTab(item.value)"
>
{{ item.label }}
</span>
</div>
<div v-loading="loading" class="tab_content">
<template v-if="!loading">
<template v-if="currentTab == 0">
<InvestmentVehicle
v-for="item in registrationPackageList"
:key="item.registrationPackageId"
:projectItem="item"
></InvestmentVehicle>
</template>
<template v-if="currentTab == 1">
<Enterprise
v-for="item in enterpriseData"
:key="item.element.itemId"
:projectItem="item"
></Enterprise>
</template>
<template v-if="currentTab == 2">
<Cluster
v-for="item in industrialAllocation"
:key="item.areaIndustrialAllocationId"
:data="item"
></Cluster>
</template>
</template>
</div>
</div>
</div>
</template>
<script setup>
import Cluster from "./Cluster.vue";
import Enterprise from "./Enterprise.vue";
import InvestmentVehicle from "../project/Project.vue";
import { getIndustryData } from "@/views/pc/industry/getRegistrationPackageList";
import { useRoute } from "vue-router"; //1.引入路由
import { onMounted } from "vue";
const $route = useRoute(); //2.实例化路由
let {
loading,
tabArr,
currentTab,
changeTab,
registrationPackageList,
enterpriseData,
industrialAllocation,
} = getIndustryData();
onMounted(() => {
loading.value = true;
if ($route.query.tab) {
changeTab($route.query.tab);
} else {
changeTab(0);
}
loading.value = false;
});
</script>
<style lang="scss" scoped>
.industry {
padding: 30px 0 58px;
background: rgba(245, 245, 245, 1);
.box {
@include size($contentWidth, auto);
margin: 0 auto;
}
.form-tab {
@include size($contentWidth, auto);
margin: 25px auto;
@include flex(row, flex-start, center, nowrap);
span {
width: 200px;
height: 82px;
@include font(24px, $color-black);
font-weight: 500;
@include flex(row, center, center, nowrap);
border-radius: 20px;
cursor: pointer;
@include border-box;
}
.active {
@include font(24px, $color-white);
background: rgba(0, 153, 255, 1);
}
}
.tab_content {
min-height: 600px;
}
}
</style>

+ 109
- 0
src/views/pc/industry/getRegistrationPackageList.js View File

@@ -0,0 +1,109 @@
import {
getPickListByKey,
getRegistrationPackageList,
getAreaIndustrialAllocationList,
getEnterpriseHomeLinkList,
} from "@/apis/index";
import { ref } from "vue";
import { toast, sortByKey } from "@/utils/common.js";
const loading = ref(true);
const tabArr = ref([{
label: "招商载体",
value: 0,
},
{
label: '企业"链家"',
value: 1,
},
{
label: "产业聚集",
value: 2,
},
]);
const currentTab = ref(0);
let registrationPackageList = ref([]);
let enterpriseData = ref([]);
let industrialAllocation = ref([]);
const pickList = [];

export function getIndustryData() {
const changeTab = async val => {
currentTab.value = val;
getData(val);
};
const getPickList = temp => {
const params = {
key: "shitiwuyeshangjia_lianjialeixing",
};
let data;
getPickListByKey(params)
.then(res => {
if (res.data.status == 0) {
data = sortByKey(res.data.data, "seq");
pickList.value = data;
enterpriseData.value = setEnterpriseData(pickList.value, temp);
console.log(enterpriseData.value);
} else {
toast(res.data.msg, "error");
}
})
.catch(error => {
console.log(error);
});
};

const getData = async tab => {
registrationPackageList.value = [];
enterpriseData.value = [];
industrialAllocation.value = [];
loading.value = true;
let data;
let res;
if (tab == 0) {
res = await getRegistrationPackageList();
} else if (tab == 1) {
res = await getEnterpriseHomeLinkList();
} else {
res = await getAreaIndustrialAllocationList();
}
try {
if (res.data.status == 0) {
data = res.data.data;
if (tab == 0) {
registrationPackageList.value = data;
} else if (tab == 1) {
getPickList(data);
} else {
industrialAllocation.value = data;
}
loading.value = false;
} else {
toast(res.data.msg, "error");
}
} catch (error) {
console.log(error);
toast("获取数据失败,请刷新重试!", "error");
}
};
const setEnterpriseData = (typeArr, data) => {
let arr = [];
typeArr.forEach(element => {
arr.push({
element,
enterpriseData: data.filter(item => item.homeLinkType.id === element.itemId),
});
});
return arr;
};

return {
loading,
tabArr,
currentTab,
changeTab,
getData,
registrationPackageList,
enterpriseData,
industrialAllocation,
};
}

+ 215
- 0
src/views/pc/policy/PreferentialPolicy.vue View File

@@ -0,0 +1,215 @@
<template>
<div class="policy" v-if="!loading">
<div class="policy-search">
<el-input
v-model="params.search"
placeholder=""
class="input-with-select"
clearable
@clear="toSearch"
@keyup.enter="toSearch"
>
<template #prepend>
<el-select
v-model="params.policyTypeId"
placeholder="全部"
@change="changeSelect"
>
<el-option label="全部" value="全部"></el-option>
<el-option
v-for="item in selectList"
:key="item.itemId"
:label="item.text"
:value="item.itemId"
/>
</el-select>
</template>
<template #append>
<el-button :icon="Search" @click="toSearch">搜索</el-button>
</template>
</el-input>
</div>
<section v-loading="dataLoading">
<ul class="policy-ul" v-if="dataList && dataList.length > 0">
<li
class="policy-item"
v-for="item in dataList"
:key="item.releasePolicyId"
@click="goRoute(item)"
>
<div class="policy-item-left">{{ item.policyName }}</div>
<div class="policy-item-right">
<span>政策类型:{{ item.policyType.text }}</span>
<span>发布时间:{{ formatDate(item.modifiedOn, "YYYY.MM.DD") }}</span>
</div>
</li>
</ul>
<section class="listNull" v-else>
<img src="@/assets/icon-no-data.png" alt="暂无数据" />
</section>
</section>

<div class="pagination" v-if="dataList && dataList.length > 0">
<el-pagination
v-model:current-page="params.page"
background
layout="prev, pager, next"
:page-size="params.pageSize"
:total="total"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>

<script setup>
import { Search } from "@element-plus/icons-vue";
import { getReleasePolicyData } from "@/views/pc/policy/policy";
import { formatDate, sortByKey, routerOpenInNewWindow } from "@/utils/common.js";
import { onMounted } from "vue";
import useHomeStore from "@/store/module/home";
const homeStore = useHomeStore();
const { params, getList, getPickList } = getReleasePolicyData();
let dataList = ref([]);
let selectList = ref([]);
let total = ref(0);
const loading = ref(true);
const dataLoading = ref(false);
const changeSelect = async val => {
params.value.policyTypeId = val == "全部" ? "" : val;
params.value.page = 1;
let dataObj = await getList(params);
dataList.value = dataObj.list;
};
const toSearch = async () => {
dataLoading.value = true;
params.value.page = 1;
params.value.pageSize = 10;
let dataObj = await getList(params);
dataList.value = dataObj.list;
dataLoading.value = false;
};

const handleCurrentChange = async val => {
params.value.page = val;
let dataObj = await getList(params);
dataList.value = dataObj.list;
};

const goRoute = val => {
routerOpenInNewWindow({
path: "/p_preferential-policy-detail",
query: {
releasePolicyId: val.releasePolicyId,
},
});
};
onMounted(async () => {
loading.value = true;

let pickArr = await getPickList();
selectList.value = sortByKey(pickArr, "seq");

let dataObj = await getList(params);
dataList.value = dataObj.list;
total.value = dataObj.total;

loading.value = false;
});
</script>
<style lang="scss" scoped>
.policy {
// width: 1180px;
padding: 30px 0 68px;
background: rgba(245, 245, 245, 1);
@include border-box;
&-search {
@include size(726px, 50px);
margin: 0 auto 25px;
border-radius: 25px;
border: 2px solid rgba(29, 112, 217, 1);
@include border-box;
background: #fff;
}
.listNull {
@include size($contentWidth, auto);
margin: 20px auto 40px;
@include flex(row, center, center, nowrap);
}
&-item {
@include size($contentWidth, 100px);
margin: 0 auto;
@include font(18px, $color-black);
background: rgba(255, 255, 255, 1);
@include flex(row, space-between, center, nowrap);
padding: 20px 18px 24px 34px;
border-radius: 25px;
@include border-box;
margin-bottom: 30px;
cursor: pointer;
&-left {
flex: 1;
@include text-ellipsis-multiple(2);
}
&-right {
height: 100%;
padding-left: 94px;
@include flex(column, space-between, flex-end, nowrap);
}
}
}
.pagination {
@include flex(row, center, center, nowrap);
}
:deep() {
.el-input-group,
.el-input,
.el-select,
.select-trigger,
.el-input__wrapper {
height: 100%;
background: transparent;
border: none !important;
box-shadow: none !important;
}
.el-select {
width: 160px;
border: none !important;
box-shadow: none !important;
}
.el-input-group--prepend
.el-input-group__prepend
.el-select
.el-input.is-focus
.el-input__wrapper {
box-shadow: none !important;
border: none;
}
.el-input__wrapper {
box-shadow: none !important;
}
.el-input-group__append {
border-top-right-radius: 25px;
border-bottom-right-radius: 25px;
background: transparent;
}
.el-input-group__prepend {
border-top-left-radius: 25px;
border-bottom-left-radius: 25px;
background: transparent;
box-shadow: none;
.el-input__wrapper {
padding-left: 25px;
}
}
.el-input-group--prepend .el-input-group__prepend .el-select:hover .el-input__wrapper {
box-shadow: none !important;
}
.el-button {
display: inline-flex;
}
.el-select .el-input__wrapper.is-focus {
box-shadow: none !important;
}
}
</style>

+ 60
- 0
src/views/pc/policy/PreferentialPolicyDetail.vue View File

@@ -0,0 +1,60 @@
<template>
<section class="detail" v-if="!loading">
<div class="title">{{ policyInfo.policyName }}</div>
<div class="subtitle">
<span>发布日期:{{ formatDate(policyInfo.modifiedOn, "YYYY年MM月DD日") }}</span>
<span>政策类型:{{ policyInfo.policyType.text }}</span>
<span>浏览量:{{ policyInfo.pageView || 0 }}</span>
</div>
<div class="content" v-html="formatHtml(policyInfo.policyText)"></div>
</section>
</template>
<script setup>
import { onMounted } from "vue";
import { getReleasePolicyData } from "@/views/pc/policy/policy";
import { formatDate } from "@/utils/common.js";
import { useRoute } from "vue-router";
import SimpleMDE from "simplemde";
const formatHtml = text => {
let html = SimpleMDE.prototype.markdown(text);
let imgUrl = `${import.meta.env.VITE_BASE_URL}/common/getImg?path=`;
html = html.replace(
/<img [^>]*src=['"]([^'"]+)[^>]*>/gi,
"<img src='" + imgUrl + "$1' width='100%'/>"
);
return html;
};
const { updatePageView, getReleasePolicyDetail } = getReleasePolicyData();
const loading = ref(true);
let policyInfo = ref({});
const $route = useRoute();
onMounted(async () => {
loading.value = true;
policyInfo.value = await getReleasePolicyDetail($route.query.releasePolicyId);
loading.value = false;
updatePageView($route.query.releasePolicyId);
});
</script>
<style lang="scss" scoped>
.detail {
@include size($contentWidth, auto);
margin: 0 auto;
padding: 60px 0 58px;
.title {
@include font(24px, $color-black);
font-weight: bold;
}
.subtitle {
@include font(18px, rgba(97, 96, 96, 1));
margin: 20px 0 30px;
span {
margin-right: 100px;
}
}
.content {
@include font(16px, $color-black);
line-height: 34px;
white-space: pre-wrap;
}
}
</style>

+ 83
- 0
src/views/pc/policy/policy.js View File

@@ -0,0 +1,83 @@
import {
getReleasePolicyList,
getPickListByKey,
updateReleasePolicyPageView,
getReleasePolicyById,
} from "@/apis/index";
import { ref } from "vue";
import { toast } from "@/utils/common.js";

export function getReleasePolicyData() {
const params = ref({
policyTypeId: "",
search: "",
page: 1,
pageSize: 10,
});
const getList = async params => {
let data;
try {
let res = await getReleasePolicyList(params.value);
if (res.data.status == 0) {
data = res.data.data;
} else {
toast(res.data.msg, "error");
}
} catch (error) {
toast("获取数据失败,请刷新重试!", "error");
}

return data;
};
const getPickList = async() => {
const params = {
key: "zhengcefabuguanli_zhengceleixing",
};
let data;
try {
let res = await getPickListByKey(params);
if (res.data.status == 0) {
data = res.data.data;
} else {
toast(res.data.msg, "error");
}
} catch (error) {
toast("获取数据失败,请刷新重试!", "error");
}

return data;
};

const getReleasePolicyDetail = async releasePolicyId => {
let data;
try {
let res = await getReleasePolicyById({ releasePolicyId });
if (res.data.status == 0) {
data = res.data.data;
} else {
toast(res.data.msg, "error");
}
} catch (error) {
console.log(error);
}
return data;
};

const updatePageView = async p => {
let data;
try {
let res = await updateReleasePolicyPageView(p);
if (res.data.status == 0) {
data = res.data.data;
} else {
toast(res.data.msg, "error");
}
} catch (error) {
console.log(error);
}

return data;
};

return { params, getList, getPickList, updatePageView, getReleasePolicyDetail };
}

+ 121
- 0
src/views/pc/project/InvestmentProjects.vue View File

@@ -0,0 +1,121 @@
<template>
<div class="investment">
<div class="container" v-if="!loading">
<van-sticky class="form-tab">
<el-tabs
class="wd-tabs"
type="card"
:stretch="true"
v-model="currentTab"
@tab-click="changeTab"
>
<el-tab-pane
v-for="(item, index) in dataList"
:key="item.attractInvestmentProjectId"
:label="item.productName"
:name="index"
></el-tab-pane>
</el-tabs>
</van-sticky>
<div
v-for="(item, index) in dataList"
:key="item.attractInvestmentProjectId"
class="form-right scroll-item"
scroll-item
:id="'tab' + index"
>
<Project :projectItem="item"></Project>
</div>
</div>
</div>
</template>
<script setup>
import Project from "./Project.vue";
import { getReleasePolicyData } from "@/views/pc/project/project";
import { onMounted, onUnmounted, ref } from "vue";
import VueScrollTo from "vue-scrollto";
import { debounce } from "@/utils/common.js";

const { getList } = getReleasePolicyData();
let dataList = ref([]);
const currentTab = ref(0);
const loading = ref(false);
const changeTab = targetName => {
currentTab.value = targetName.paneName;
VueScrollTo.scrollTo(`#tab${targetName.paneName}`, 500, { offset: -84 });
};

const scrollListenerHandler = debounce(() => {
const scrollTop =
window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; // 滚动条偏移量
document.querySelectorAll(".scroll-item").forEach((item, index) => {
if (item.offsetTop - 84 <= scrollTop) {
currentTab.value = index;
}
});
}, 100);
onMounted(async () => {
loading.value = true;
dataList = await getList();
loading.value = false;
window.addEventListener("scroll", scrollListenerHandler);
});
onUnmounted(() => {
window.removeEventListener("scroll", scrollListenerHandler);
});
</script>
<style lang="scss" scoped>
.investment {
padding: 30px 0 58px;
background: rgba(245, 245, 245, 1);
}
.container {
@include size($contentWidth, auto);
margin: 0 auto;
// @include flex(row, space-between, flex-start, nowrap);

.form-tab {
margin: 24px auto;
}

.form-right {
width: 100%;
}
:deep() {
.el-tabs--card > .el-tabs__header {
background: rgba(245, 245, 245, 1);
height: 82px;
border: none;
}
.el-tabs__nav-next,
.el-tabs__nav-prev {
line-height: 82px;
}
.el-tabs--card > .el-tabs__header .el-tabs__nav {
border: none;
}
.el-tabs--card > .el-tabs__header .el-tabs__item {
width: auto;
height: 82px;
border-radius: 20px;
border: none;
@include font(24px, $color-black);
font-weight: 500;
}
.el-tabs--card > .el-tabs__header .el-tabs__item.is-active {
@include font(24px, $color-white);
background: rgba(0, 153, 255, 1);
}
.el-tabs__item {
// width: 200px;
// height: 82px;
// @include font(24px, $color-black);
// font-weight: 500;
// @include flex(row, center, center, nowrap);
// border-radius: 20px;
// cursor: pointer;
// @include border-box;
}
}
}
</style>

+ 74
- 0
src/views/pc/project/Project.vue View File

@@ -0,0 +1,74 @@
<template>
<div class="project">
<img :src="formatImg(propsData.projectItem.productUrl[0])" alt="招商项目图片" />
<div class="project-right">
<div class="content">
<span class="content-title">{{ propsData.projectItem.productName }}</span>
<div class="content-content" :title="propsData.projectItem.productText">
{{ propsData.projectItem.productText }}
</div>
</div>
<span class="project-right-consult" @click="showTipInfo">立 即 咨 询</span>
</div>
</div>
<TipDialog ref="tipRefPC"></TipDialog>
</template>
<script setup>
import { formatImg } from "@/utils/common.js";
const propsData = defineProps({
projectItem: {
type: Object,
},
});

import TipDialog from "@/components/pc/TipDialogPC.vue";
const tipRefPC = ref(null);
const showTipInfo = () => {
tipRefPC.value.showTip();
};
</script>
<style lang="scss" scoped>
.project {
background: rgba(255, 255, 255, 1);
border-radius: 20px;
padding: 24px 12px;
@include border-box;
@include flex(row, space-between, center, nowrap);
margin-bottom: 24px;

img {
@include size(526px, 460px);
margin-right: 32px;
}
&-right {
flex: 1;
@include size(auto, 460px);
@include flex(column, space-between, flex-start, nowrap);
.content {
flex: 1;
padding-bottom: 20px;
@include flex(column, flex-start, flex-start, nowrap);
&-title {
@include font(24px, $color-black);
font-weight: bold;
margin-bottom: 25px;
}
&-content {
@include font(18px, $color-black);
line-height: 30px;
@include text-ellipsis-multiple(11);
white-space: pre-wrap;
}
}

&-consult {
@include size(100%, 50px);
border-radius: 50px;
background: rgba(0, 153, 255, 1);
@include flex(row, center, center, nowrap);
@include font(20px, $color-white);
cursor: pointer;
}
}
}
</style>

+ 23
- 0
src/views/pc/project/project.js View File

@@ -0,0 +1,23 @@
import { getAttractInvestmentProjectList } from "@/apis/index";
import { ref } from "vue";
import { toast } from "@/utils/common.js";

export function getReleasePolicyData() {
const getList = async() => {
let data;
try {
let res = await getAttractInvestmentProjectList();
if (res.data.status == 0) {
data = res.data.data;
} else {
toast(res.data.msg, "error");
}
} catch (error) {
toast("获取数据失败,请刷新重试!", "error");
}

return ref(data);
};

return { getList };
}

+ 77
- 0
vite.config.js View File

@@ -0,0 +1,77 @@
import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
// 按需引入 ElementPlus vant
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import { VantResolver } from "unplugin-vue-components/resolvers";

// 生成.gz文件
import viteCompression from "vite-plugin-compression";

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
let env = loadEnv(mode, process.cwd());
return {
plugins: [
vue(),
AutoImport({
// 自动导入vue相关的Api
imports: ["vue", "vue-router"],
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver(), VantResolver()],
}),
{
...viteCompression(),
apply: "build",
},
],
resolve: {
// 配置路径别名
alias: {
"@": resolve(__dirname, "./src"),
},
},
css: {
// CSS 预处理器
preprocessorOptions: {
//define global scss variable
scss: {
javascriptEnabled: true,
additionalData: `@import "@/styles/variables.scss";`,
},
},
},

server: {
host: "0.0.0.0",
port: 8080,
open: true,
https: false,
proxy: {
"/api": {
target: env.VITE_BASE_URL,
changeOrigin: true,
ws: true,
rewrite: path => path.replace(/^\/api/, ""),
},
},
},
build: {
rollupOptions: {
output: {
manualChunks: {
vue: ["vue", "pinia", "vue-router"],
elementIcons: ["@element-plus/icons-vue"],
},
chunkFileNames: "wap/js/[name]-[hash].js",
entryFileNames: "wap/js/[name]-[hash].js",
assetFileNames: "wap/[ext]/[name]-[hash].[ext]",
},
},
},
};
});

Loading…
Cancel
Save