Commit 8a5237c3 authored by shilei's avatar shilei

Merge branch 'dev' of http://101.201.78.203/cfld-front/liyeyun into dev

parents e349f6bb 6ddd3cc3
......@@ -50,7 +50,7 @@ module.exports = {
'error',
{
script: {
lang: 'ts',
lang: ['ts', 'tsx'],
},
},
],
......
......@@ -7,41 +7,39 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
CommonHeader: typeof import('./src/components/common/CommonHeader.vue')['default']
copy: typeof import('./src/components/list/list-land-item copy.vue')['default']
DetailInfoCell: typeof import('./src/components/detail/DetailInfoCell.vue')['default']
DetailMain: typeof import('./src/components/detail/DetailMain.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElFooter: typeof import('element-plus/es')['ElFooter']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
Filter: typeof import('./src/components/filter/filter.vue')['default']
FilterView: typeof import('./src/components/filter/filter-view.vue')['default']
ListCarrierItem: typeof import('./src/components/list/list-carrier-item.vue')['default']
ListIndustryCategory: typeof import('./src/components/list/list-industryCategory.vue')['default']
ListLand: typeof import('./src/components/list/list-land.vue')['default']
ListLandItem: typeof import('./src/components/list/list-land-item.vue')['default']
ListPagination: typeof import('./src/components/list/list-pagination.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
CommonHeader: (typeof import('./src/components/common/CommonHeader.vue'))['default'];
DetailInfoCell: (typeof import('./src/components/detail/DetailInfoCell.vue'))['default'];
DetailMain: (typeof import('./src/components/detail/DetailMain.vue'))['default'];
ElButton: (typeof import('element-plus/es'))['ElButton'];
ElCheckbox: (typeof import('element-plus/es'))['ElCheckbox'];
ElCheckboxGroup: (typeof import('element-plus/es'))['ElCheckboxGroup'];
ElCol: (typeof import('element-plus/es'))['ElCol'];
ElContainer: (typeof import('element-plus/es'))['ElContainer'];
ElDatePicker: (typeof import('element-plus/es'))['ElDatePicker'];
ElFooter: (typeof import('element-plus/es'))['ElFooter'];
ElForm: (typeof import('element-plus/es'))['ElForm'];
ElFormItem: (typeof import('element-plus/es'))['ElFormItem'];
ElHeader: (typeof import('element-plus/es'))['ElHeader'];
ElInput: (typeof import('element-plus/es'))['ElInput'];
ElMain: (typeof import('element-plus/es'))['ElMain'];
ElMenu: (typeof import('element-plus/es'))['ElMenu'];
ElMenuItem: (typeof import('element-plus/es'))['ElMenuItem'];
ElOption: (typeof import('element-plus/es'))['ElOption'];
ElPagination: (typeof import('element-plus/es'))['ElPagination'];
ElRadio: (typeof import('element-plus/es'))['ElRadio'];
ElRadioGroup: (typeof import('element-plus/es'))['ElRadioGroup'];
ElSelect: (typeof import('element-plus/es'))['ElSelect'];
ElSubMenu: (typeof import('element-plus/es'))['ElSubMenu'];
ImageList: (typeof import('./src/components/common/ImageList.vue'))['default'];
ListInfo: (typeof import('./src/components/list/ListInfo.vue'))['default'];
Filter: (typeof import('./src/components/filter/filter.vue'))['default'];
FilterView: (typeof import('./src/components/filter/filter-view.vue'))['default'];
ListCarrierItem: (typeof import('./src/components/list/list-carrier-item.vue'))['default'];
ListPagination: (typeof import('./src/components/list/ListPagination.vue'))['default'];
MiniListItem: (typeof import('./src/components/detail/MiniListItem.vue'))['default'];
ListLandItem: (typeof import('./src/components/list/list-land-item.vue'))['default'];
ParkItem: (typeof import('./src/components/list/ParkItem.vue'))['default'];
RouterLink: (typeof import('vue-router'))['RouterLink'];
RouterView: (typeof import('vue-router'))['RouterView'];
}
}
This diff is collapsed.
......@@ -14,13 +14,14 @@
"format": "prettier --write src/"
},
"dependencies": {
"@vueuse/core": "^10.6.1",
"@amap/amap-jsapi-loader": "^1.0.1",
"@vueuse/core": "^10.6.1",
"axios": "^1.6.2",
"big.js": "^6.2.1",
"element-plus": "^2.4.2",
"lodash-es": "^4.17.21",
"pinia": "^2.1.7",
"swiper": "^11.0.5",
"vue": "^3.3.8",
"vue-router": "^4.2.5"
},
......@@ -51,18 +52,12 @@
"postcss-scss": "^4.0.9",
"prettier": "^3.1.0",
"sass": "^1.69.5",
"stylelint": "^15.11.0",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-recommended-scss": "^13.1.0",
"stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard": "^34.0.0",
"tailwindcss": "^3.3.5",
"typescript": "~5.2.2",
"unplugin-auto-import": "^0.16.7",
"unplugin-vue-components": "^0.25.2",
"vite": "^5.0.0",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-stylelint": "^5.2.1",
"vitest": "^0.34.6",
"vue-tsc": "^1.8.22"
}
......
<template>
<div>
<ElContainer class="min-h-screen">
<ElHeader>
<CommonHeader :menu-list="menuList" :current-path="currentPath"></CommonHeader>
<ElMain>
</ElHeader>
<ElMain class="!p-0">
<RouterView />
</ElMain>
<ElFooter>Footer</ElFooter>
......
......@@ -15,3 +15,17 @@
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
@layer components {
// 列表页,招商方向标签
.investment-direction {
@apply flex h-[22px] items-center justify-center rounded-sm bg-[#2c68ff14] px-3 text-sm text-[#2C68FF];
}
.status-tag {
@apply h-[22px] items-center justify-center rounded-sm px-3 text-sm flex;
}
.main-width {
@apply w-[926px];
}
}
......@@ -59,6 +59,6 @@ const handleMenuChange = () => {};
</script>
<style scoped>
.header-view {
box-shadow: 0px 2px 8px 0px rgba(90, 0, 0, 0.1);
box-shadow: 0 2px 8px 0 rgba(90 0 0 0.1);
}
</style>
<template>
<div v-if="images.length" class="w-[455px]">
<div class="w-full">
<Swiper
centered-slides
:modules="[Thumbs]"
:thumbs="{ swiper: thumbsSwiper }"
@swiper="setMainSwiper"
@slide-change="handleSlideChange"
>
<SwiperSlide v-for="(image, index) of images" :key="index">
<img class="h-[342px] w-full" :src="image" />
</SwiperSlide>
</Swiper>
</div>
<div class="relative mt-[13px]">
<div>
<Swiper
:modules="[Thumbs]"
:slides-per-view="4.3"
watch-slides-progress
free-mode
@swiper="setThumbsSwiper"
>
<SwiperSlide v-for="(image, index) of images" :key="index">
<img
class="h-[72px] w-[90px] cursor-pointer rounded"
:class="{ 'border border-[#2878ff] p-1': index === currentIndex }"
:src="image"
/>
</SwiperSlide>
</Swiper>
</div>
<template v-if="images.length > 4">
<div
class="image-icon-bg__left absolute left-0 top-0 z-10 flex h-[72px] w-[46px] cursor-pointer items-center"
@click="changeCurrent(-1)"
>
<img class="ml-1 h-[30px] w-[30px]" src="@/assets/images/image-left.png" />
</div>
<div
class="image-icon-bg__right absolute right-0 top-0 z-10 flex h-[72px] w-[46px] cursor-pointer items-center"
@click="changeCurrent(1)"
>
<img class="mr-1 h-[30px] w-[30px]" src="@/assets/images/image-right.png" />
</div>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import 'swiper/css';
import 'swiper/css/thumbs';
import { Thumbs } from 'swiper/modules';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { ref } from 'vue';
import type { Swiper as SwiperType } from 'swiper/types';
withDefaults(
defineProps<{
images: string[];
}>(),
{
images: () => [],
},
);
const thumbsSwiper = ref<null | SwiperType>(null);
const mainSwiper = ref<null | SwiperType>(null);
const currentIndex = ref(0);
const changeCurrent = (index: number) => {
if (index > 0) {
mainSwiper.value?.slideNext();
} else {
mainSwiper.value?.slidePrev();
}
};
const setThumbsSwiper = (swiper: SwiperType) => {
thumbsSwiper.value = swiper;
};
const setMainSwiper = (swiper: SwiperType) => {
mainSwiper.value = swiper;
};
const handleSlideChange = (swiper: SwiperType) => {
currentIndex.value = swiper.activeIndex;
};
</script>
<style lang="scss">
.image-icon-bg {
&__left {
background: linear-gradient(90deg, rgba(255, 255, 255, 0.6) 0%, rgba(255, 255, 255, 0) 100%);
}
&__right {
background: linear-gradient(270deg, rgba(255, 255, 255, 0.6) 0%, rgba(255, 255, 255, 0) 100%);
}
}
</style>
<template>
<div v-if="infos.length > 0">
<div v-for="(line, index) of infos" :key="index" class="min-h-10 flex">
<div v-for="col in line" :key="col.name" class="flex flex-1 text-xs text-[#1A1A1A]">
<div class="flex w-[160px] items-center bg-[#FAFAFC] px-[30px] py-[10px] leading-5">
{{ col.name }}
</div>
<div
class="flex items-center px-[30px] py-[10px] leading-5 outline outline-1 outline-[#FAFAFC]"
:class="line.length > 1 || !col.oneline ? 'w-[303px]' : 'flex-1'"
>
{{ handleValue(col) }}
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { haveValue } from '@/utils/filters.ts';
export interface Info {
name: string;
value: string | number;
unit?: string;
/** 是否独占一行 */
oneline?: boolean;
}
export type Infos = Info[][] | [];
withDefaults(
defineProps<{
infos: Infos;
}>(),
{
infos: () => [],
},
);
const handleValue = (col: Info) => {
const { value, unit = '' } = col;
return haveValue(value) ? value.toString() + unit : '--';
};
</script>
<!-- 详情页下方信息块 -->
<template>
<section class="bg-white p-4 shadow">
<h2 v-if="title">{{ title }}</h2>
<div v-if="toListText" class="flex justify-end text-xs" @click="handleToList">更多</div>
<div>
<section class="bg-white">
<h2 v-if="title" class="flex h-8 items-center" :class="{ 'justify-center': titleCenter }">
<div v-if="!titleCenter" class="h-[18px] w-[3px] rounded-[30px] bg-[#C0322B]"></div>
<div
class="ml-[13px] font-yahei text-xl font-bold leading-8 tracking-[2px] text-[#1A1A1A]"
:class="{ 'text-2xl': titleCenter }"
>
{{ title }}
</div>
</h2>
<div class="relative flex h-[17px] items-center justify-center">
<div v-if="titleCenter" class="title-bottom-bg h-1 w-[102px] rounded-[30px]"></div>
<div
v-if="toListText"
class="absolute right-0 flex cursor-pointer justify-end text-xs"
@click="handleToList"
>
更多
</div>
</div>
<div class="mt-5">
<slot></slot>
</div>
</section>
......@@ -15,10 +32,13 @@ export interface DetailInfoCellProps {
title?: string;
/** 跳转列表按钮文案 */
toListText?: string;
/** 是否标题居中 */
titleCenter?: boolean;
}
withDefaults(defineProps<DetailInfoCellProps>(), {
title: '',
toListText: '',
titleCenter: false,
});
const emit = defineEmits(['to-list']);
......@@ -27,3 +47,14 @@ const handleToList = () => {
emit('to-list');
};
</script>
<style lang="scss">
.title-bottom-bg {
background: linear-gradient(
90deg,
#c0322b 0%,
rgba(192, 50, 43, 0.45) 66%,
rgba(192, 50, 43, 0) 100%
);
}
</style>
<template>
<div>
<div class="flex items-center space-x-[5px]">
<div
v-for="(tab, index) of tabs"
:key="tab.name"
class="flex h-8 cursor-pointer items-center justify-center px-5 text-sm leading-4"
:class="{ 'text-[#C0322B]': index === modelValue }"
@click="handleTabChange(index)"
>
<div>{{ tab.name }}</div>
<div>{{ tab.count }}</div>
</div>
</div>
<slot></slot>
</div>
</template>
<script setup lang="ts">
export interface InfoTab {
name: string;
count: number;
}
withDefaults(
defineProps<{
tabs: InfoTab[];
modelValue: number;
}>(),
{
tabs: () => [],
modelValue: 0,
},
);
const emit = defineEmits(['update:modelValue']);
const handleTabChange = (index: number) => {
emit('update:modelValue', index);
};
</script>
<!-- 主图部分 -->
<template>
<div v-if="detail">
<div class="flex h-12 items-center justify-between">
<div class="flex h-full items-center">
<div>
<img class="h-10 w-auto" src="@/assets/images/logo-head.png" alt="立业云" />
</div>
<div
class="ml-4 flex h-8 items-center px-4 font-yahei text-base text-[#C0322B] outline outline-1 outline-[#C0322B]"
>
{{ headerType }}
</div>
</div>
<div class="h-full">
<div class="search-input-shadow flex h-full items-center overflow-hidden rounded">
<div class="flex flex-1 items-center justify-center">
<input
class="search-input h-full w-[375px] px-3 py-[14px] text-sm"
type="text"
:placeholder="`请输入${headerType}名称进行搜索`"
/>
</div>
<div class="flex h-full w-[70px] items-center justify-center bg-[#C0322B]">
<img class="h-[30px] w-[30px]" src="@/assets/images/icon-search.png" alt="search" />
</div>
</div>
</div>
</div>
<div class="mt-5 py-5">
<div class="flex h-10 items-center">
<div class="font-yahei text-[32px] font-bold leading-10 text-[#333333]">
{{ detail.name }}
</div>
<div v-if="currentStatusTag" class="ml-2">
<div :class="['status-tag', currentStatusTag.className]">
{{ currentStatusTag.name }}
</div>
</div>
<div v-if="superTag" class="ml-2">
<div :class="['status-tag', superTag.className]">
{{ superTag.name }}
</div>
</div>
</div>
<!-- main content -->
<div class="flex">
<div>
<img src="" alt="" />
<div class="mt-4">
<ImageList :images="detail.imgUrlList"></ImageList>
</div>
<div class="ml-4">
<div>
<slot></slot>
</div>
<div class="mt-3">
<div class="flex">
<div class="flex h-[22px] items-center font-yahei text-xs leading-5 text-[#4D4D4D]">
招商方向
</div>
<div class="ml-[30px] flex flex-1 flex-wrap gap-[10px]">
<div
v-for="invest of detail.investmentDetails"
:key="invest.codeName"
class="flex h-[22px] items-center rounded-sm bg-[#4F4F4F14] px-3 text-xs leading-[18px] text-[#4F4F4F]"
>
{{ invest.codeName }}
</div>
</div>
</div>
<div class="mt-[13px] flex items-center">
<div class="flex h-[22px] items-center font-yahei text-xs leading-5 text-[#4D4D4D]">
所属地区
</div>
<div class="ml-[30px] font-yahei text-xs leading-5 text-[#1A1A1A]">{{ area }}</div>
</div>
<div class="mt-2 flex items-center">
<div class="flex h-[22px] items-center font-yahei text-xs leading-5 text-[#4D4D4D]">
详细地址
</div>
<div class="ml-[30px] font-yahei text-xs leading-5 text-[#1A1A1A]">
{{ detail.address }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup></script>
<script lang="ts" setup>
import { useItemStatus } from '@/composable/useItemStatus.ts';
import { AuthStatus, DetailType } from '@/types/enum.ts';
import { computed } from 'vue';
import ImageList from '@/components/common/ImageList.vue';
import type { Investment } from '@/types/common.ts';
import { haveValue } from '@/utils/filters.ts';
export interface HeaderDetail {
name: string;
status?: AuthStatus;
statusName?: string;
isSuper?: number;
isSuperName?: string;
imgUrlList: string[];
/** 招商方向 */
investmentDetails: Investment[];
/** 详细地址 */
address: string;
/** 省份 */
provinceName: string;
/** 城市 */
cityName: string;
/** 区/县 */
regionName: string;
}
const props = withDefaults(
defineProps<{
type: DetailType;
detail: HeaderDetail;
}>(),
{},
);
const { currentStatusTag, superTag } = useItemStatus(props.detail);
/** 类型 */
const headerType = computed(() => {
const config = {
[DetailType.carrier]: '载体',
[DetailType.develop]: '开发区',
[DetailType.industrial]: '产业园',
[DetailType.land]: '土地',
[DetailType.startDevelop]: '',
};
return config[props.type];
});
/** 省市区 */
const area = computed(() => {
const { provinceName, cityName, regionName } = props.detail;
return [provinceName, cityName, regionName].filter(haveValue).join('-');
});
</script>
<style lang="scss">
.search-input-shadow {
box-shadow: 0px 2px 8px 0px rgba(90, 0, 0, 0.1);
}
.search-input {
&:focus {
outline: none;
}
&::placeholder {
@apply text-[#999999];
}
}
</style>
<!-- 产业园、开发区内的载体、土地项 -->
<template>
<div v-if="detail" class="w-[214px]">
<div @click="handleDetail">
<img class="h-[161px] w-full cursor-pointer rounded-sm" :src="detail.imgUrl" />
</div>
<div class="mt-3 cursor-pointer font-yahei text-base text-[#1A1A1A]" @click="handleDetail">
{{ detail.name }}
</div>
<div class="mt-1 font-yahei text-[10px] leading-[18px] text-[#4D4D4D]">
{{ detail.info }}
</div>
</div>
</template>
<script lang="ts" setup>
export interface MiniDetail {
imgUrl: string;
name: string;
info: string;
id: number;
}
withDefaults(
defineProps<{
detail: MiniDetail;
}>(),
{},
);
const emit = defineEmits(['to-detail']);
const handleDetail = () => {
emit('to-detail');
};
</script>
<!-- 竖线隔开的信息 -->
<script lang="tsx">
import { haveValue } from '@/utils/filters.ts';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'ListInfo',
props: {
infos: {
type: Array,
default: () => [] as string[],
},
},
setup(props) {
return () => (
<div class="flex items-center space-x-5 text-[#4d4d4d]">
{props.infos.filter(haveValue).reduce((pre, info) => {
return (
<>
{pre}
{pre ? <div class="text-xs leading-[18px]">|</div> : ''}
<div class="font-yahei text-xs leading-5">{info}</div>
</>
);
}, '')}
</div>
);
},
});
</script>
<template>
<div>
<ElPagination
class="justify-center"
layout="prev, pager, next"
:current-page="currentPage"
:page-size="pageSize"
......@@ -12,6 +13,8 @@
</template>
<script setup lang="ts">
import { useWindowScroll } from '@vueuse/core';
withDefaults(
defineProps<{
currentPage: number;
......@@ -21,15 +24,23 @@ withDefaults(
{},
);
const { y } = useWindowScroll({ behavior: 'smooth' });
const emit = defineEmits<{
'update:current-page': [page: number];
'update:page-size': [page: number];
}>();
const scrollTop = () => {
y.value = 0;
};
const handlePageChange = (page: number) => {
scrollTop();
emit('update:current-page', page);
};
const handlePageSizeChange = (size: number) => {
scrollTop();
emit('update:page-size', size);
};
</script>
<template>
<div v-if="item">
<div class="flex min-h-[156px]">
<div @click="handleDetail">
<img
class="h-[156px] w-[208px] cursor-pointer rounded-sm"
:src="item.imgUrl"
:alt="isDevelop ? '开发区' : '产业园'"
/>
</div>
<div class="ml-4 flex-1 py-2">
<div class="flex items-center">
<div
class="cursor-pointer text-xl font-semibold leading-6 text-[#1a1a1a]"
@click="handleDetail"
>
{{ item.name }}
</div>
<div v-if="currentStatusTag" class="ml-2">
<div :class="['status-tag', currentStatusTag.className]">
{{ currentStatusTag.name }}
</div>
</div>
<div v-if="superTag" class="ml-2">
<div :class="['status-tag', superTag.className]">
{{ superTag.name }}
</div>
</div>
</div>
<div class="mt-[17px] font-yahei text-xs leading-5 text-[#4d4d4d]">
{{ filterAddress(item) }}
</div>
<div class="mt-4">
<ListInfo :infos="infos"></ListInfo>
</div>
<div class="mt-[17px] flex flex-wrap gap-[13px]">
<div v-for="direction in directions" :key="direction" class="investment-direction">
{{ direction }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useItemStatus } from '@/composable/useItemStatus.ts';
import type { IndustrialParkItem } from '@/types/api/industrialParkList.ts';
import { filterAddress } from '@/utils/filters.ts';
import { computed } from 'vue';
import type { DevelopZoneItem } from '@/types/api/developZoneList';
import { ParkItemType } from '@/types/enum.ts';
import ListInfo from './ListInfo.vue';
const props = withDefaults(
defineProps<{
item: Partial<IndustrialParkItem & DevelopZoneItem>;
type: ParkItemType;
}>(),
{},
);
const emit = defineEmits<{
'to-detail': [];
}>();
const { currentStatusTag, superTag } = useItemStatus(props.item);
/** 最大展示招商方向数量 */
const MAX_DIRECTION_COUNT = 3;
const isDevelop = computed(() => {
return props.type === ParkItemType.develop;
});
const infos = computed(() => {
const { levelName, planArea, type } = props.item || {};
return [levelName, type, planArea ? `${planArea}亩` : ''];
});
const directions = computed(() => {
return props.item.investmentDirection?.slice?.(0, MAX_DIRECTION_COUNT) || [];
});
const handleDetail = () => {
emit('to-detail');
};
</script>
import type { Info, Infos } from '@/components/detail/DetailInfo.vue';
import type { InfoTab } from '@/components/detail/DetailInfoTab.vue';
import type { ListInfo } from '@/types/common';
import { computed, ref, type ComputedRef } from 'vue';
/**
* 带tab的详情信息块
*/
export function useInfoTab<T>(getInfo: (item: T) => Info[], list: ComputedRef<ListInfo<T>[]>) {
const index = ref(0);
const tabs = computed<InfoTab[]>(() => {
const result =
list.value.map(({ type, info }) => {
return {
count: info.length,
name: type,
};
}) || [];
return result;
});
const infos = computed<Infos[]>(() => {
const result =
list.value.map(({ info }) => {
return info.map(getInfo);
}) || [];
return result;
});
return [index, tabs, infos] as [typeof index, typeof tabs, typeof infos];
}
import type { AuthStatus } from '@/types/enum';
import { computed } from 'vue';
export interface StatusConfig {
[index: number]: {
className: string;
};
}
/**
* 产业园、开发区状态
*/
export function useItemStatus(item: {
status?: AuthStatus;
statusName?: string;
isSuper?: number;
isSuperName?: string;
}) {
const statusConfig: StatusConfig = {
3: {
className: 'text-[#0bdba8] bg-[#0bdba814]',
},
};
/** 星级园区配置 */
const superStarConfig = {
className: 'text-[#ffb540] bg-[#ffb54014]',
};
/** 当前状态标签 */
const currentStatusTag = computed(() => {
return item.status ? { ...statusConfig[item.status], name: item.statusName } : null;
});
/** 星级开发区标签 */
const superTag = computed(() => {
return item.isSuper === 1
? {
...superStarConfig,
name: item.isSuperName || '星级开发区',
}
: null;
});
return {
currentStatusTag,
superTag,
};
}
import type { Infos } from '@/components/detail/DetailInfo.vue';
import { computed, type ComputedRef } from 'vue';
export interface JoinRequiredParams {
[index: string]: any;
// 开发区产业园名称不一样
/** 禁限目录 */
prohibitedDirectory?: string;
/** 禁限目录 */
prohibitDirectory?: string;
/** 环评要求 */
environmentalEquirements?: string;
/** 总投资额 */
investmentAmountTotal: number | string;
/** 亩均投资强度 */
investmentStrength: string;
/** 亩均税收 */
taxationStrength: string;
/** 能评要求 */
valueAddedEnergy: string;
/** 亩均年产值 */
averageOutputValue: string;
/** 建设周期 */
buildingPeriod: number | string;
}
/**
* 详情页入驻要求, 开发区、产业园, 土地,载体
* @param {boolean} isFilter 是否过滤环评要求和禁限目录(土地、载体)
*/
export function useJoinRequired<T extends JoinRequiredParams>(
detail: ComputedRef<T | null>,
isFilter = false,
) {
/** 入驻要求 */
const joinRequired = computed(() => {
if (detail.value) {
const {
investmentAmountTotal,
investmentStrength,
taxationStrength,
valueAddedEnergy,
averageOutputValue,
buildingPeriod,
prohibitedDirectory,
prohibitDirectory,
environmentalEquirements,
} = detail.value;
const result: Infos = [
[
{
name: '总投资额',
value: investmentAmountTotal,
unit: '亿元',
},
{
name: '亩均年产值',
value: averageOutputValue,
unit: '万/亩',
},
],
[
{
name: '亩均投资强度',
value: investmentStrength,
unit: '万/亩',
},
{
name: '亩均税收',
value: taxationStrength,
unit: '万/亩',
},
],
[
{
name: '建设周期',
value: buildingPeriod,
unit: '月',
},
{
name: '能评要求',
value: valueAddedEnergy,
unit: 'tce/万元',
},
],
...(isFilter
? []
: [
[
{
name: '环评要求',
value: environmentalEquirements || '',
oneline: true,
},
],
[
{
name: '禁限目录',
value: prohibitedDirectory || prohibitDirectory || '',
oneline: true,
},
],
]),
];
return result;
} else {
return [];
}
});
return { joinRequired };
}
import type { RouteLocationRaw } from 'vue-router';
import { useRouter } from 'vue-router';
/**
* 路由跳转
*/
export function useJump() {
const router = useRouter();
/**
* @param isNew 是否在新窗口打开
*/
const open = (path: RouteLocationRaw, isNew = true) => {
const { href } = router.resolve(path);
window.open(href, isNew ? '_blank' : '_self');
};
return {
router,
open,
};
}
......@@ -2,15 +2,15 @@ import { useQueryList } from '@/composable/useQueryList.ts';
import { ref, watchEffect, type Ref } from 'vue';
import { RequestUrl } from '@/types/api.ts';
import type { CommonListResp, CommonListParams } from '@/types/common';
import { useRouter } from 'vue-router';
import type { RouteName } from '@/router/router.ts';
import { useRequest } from './useRequest.ts';
import { useJump } from './useJump.ts';
export function useListView<
T extends { id: number; [index: string]: any },
C extends Record<any, any>,
>(url: RequestUrl, searchParams: any = {}) {
const router = useRouter();
const { open } = useJump();
const { request: requestList } = useRequest<CommonListResp<T>, CommonListParams<C>>(url, {
method: 'POST',
});
......@@ -33,7 +33,7 @@ export function useListView<
* 跳转详情页
*/
const handleDetail = (name: RouteName, item: T) => {
router.push({
open({
name,
query: {
id: item.id,
......
......@@ -31,6 +31,8 @@ const IndustrialParkDetail = () => import('@/views/industrial-park/IndustrialPar
const DevelopZoneDetail = () => import('@/views/develop/DevelopZoneDetail.vue');
/** 土地详情 */
const LandDetail = () => import('@/views/land/LandDetail.vue');
/** 载体详情 */
const CarrierDetail = () => import('@/views/carrier/CarrierDetail.vue');
export const enum RouteName {
home = 'home',
......@@ -62,6 +64,8 @@ export const enum RouteName {
developZoneDetail = 'develop-zone-detail',
/** 土地详情 */
landDetail = 'land-detail',
/** 载体详情 */
carrierDetail = 'carrier-detail',
}
function gen(name: RouteName) {
......@@ -175,6 +179,11 @@ const routes: readonly RouteRecordRaw[] = [
...gen(RouteName.landDetail),
component: LandDetail,
},
{
// 开发区详情
...gen(RouteName.carrierDetail),
component: CarrierDetail,
},
{
path: '/:pathMatch(.*)*',
redirect: '/',
......
......@@ -4,7 +4,7 @@
export enum RequestUrl {
login = '/login',
/** 产业园列表 */
industrialParkList = '/lyy/parkInfo/v1.0/page',
industrialParkList = '/lyy/api/parkInfo/v1.0/page',
/** 产业园详情 */
industrialParkDetal = '/lyy/api/parkInfo/v1.0/detail',
/** 开发区列表 */
......
......@@ -49,7 +49,8 @@ export interface IndustrialParkItem {
/** 城市 */
cityName: string;
/** 占地面积(单位:亩) */
coverArea: number;
// coverArea: number;
planArea: number;
/** 主键id */
id: number;
/** 图片链接 */
......@@ -71,7 +72,8 @@ export interface IndustrialParkItem {
/** 区/县 */
regionName: string;
/** 认证状态(1-未认证;2-认证中;3-已认证;4-未通过;5-待认证;9-已作废) */
status: number;
status: AuthStatus;
/** 认证状态(1-未认证;2-认证中;3-已认证;4-未通过;5-待认证;9-已作废) */
statusName: string;
type: string;
}
import type { Investment } from '../common.ts';
/**
* 载体详情
*/
export interface CarrierDetailResp {
/** 建筑面积 */
area: number;
/** 产业园信息 / 入住要求 */
carrierParkVo: {
/** 详细地址 */
address: string;
/** 亩均产值(单位:万/亩) */
averageOutputValue: string;
/** 建设周期(月) */
buildingPeriod: number;
/** 城市code */
cityCode: string;
/** 城市 */
cityName: string;
/** 联系人 */
contactPerson: string;
/** 占地面积(单位:亩) */
coverArea: number;
/** 主键id */
id: number;
/** 图片 */
imgUrl: string;
/** 总投资额(亿元) */
investmentAmountTotal: number;
/** 招商方向 */
investmentDetails: Investment[];
/** 投资强度(单位:万/亩) */
investmentStrength: string;
/** 纬度(坐标系gcj02) */
latitude: number;
/** 园区级别名称 */
levelName: string;
/** 经度(坐标系gcj02) */
longitude: number;
/** 载体名称 */
name: string;
/** 省份code */
provinceCode: string;
/** 省份 */
provinceName: string;
/** 区/县code */
regionCode: string;
/** 区/县 */
regionName: string;
/** 税收强度(单位:万/亩) */
taxationStrength: string;
/** 能评要求(单位:tce/万元) */
valueAddedEnergy: string;
};
/** 柱距(m) */
distance: number;
/** 电梯类型 */
elevatorType: string;
/** 消防等级 */
fireProtectionLevel: string;
/** 耐火等级 */
fireResistant: string;
/** 层高(m) */
floorHigh: number;
/** 层数 */
floorNumber: number;
/** 主键id */
id: number;
/** 轮播图 */
imgUrls: string[];
/** 招商信息 */
investmentDetails: {
/** 招商类型 */
code: number;
/** 招商类型名称 */
codeName: string;
/** 招商详情 */
details: string;
}[];
/** 用户解锁额度剩余次数 */
limitNum: number;
/** 载体名称 */
name: string;
/** 单层面积(㎡) */
singleArea: number;
/** 建筑结构 */
structure: string;
/** 类型 */
type: string;
/** 轮播视频 */
videoUrlList: {
/** */
videoCover: string;
/** */
videourl: string;
}[];
/** 承重(单位:kg/㎡) */
weight: number;
}
import type { Enterprise, Investment, ListInfo, Policy } from '../common.ts';
import type { AuthStatus } from '../enum.ts';
/**
* 开发区详情
*/
export interface DevelopZoneDetailResp {
/** 详细地址 */
address: string;
/** 亩均年产值(单位:万/亩) */
averageOutputValue: string;
/** 投资周期(月) */
buildingPeriod: number;
/** 市code */
cityCode: string;
/** 市 */
cityName: string;
/** 招商对接人 */
contactPerson: string;
/** 联系电话 */
contactPhone: string;
/** 开发区简介 */
description: string;
/** 重点企业详情 */
enterpriseList: ListInfo<Enterprise>[];
/** 环评要求 */
environmentalEquirements: string;
/** 产业基金 */
fundList: {
/** 投资方向 */
investmentDirection: string;
/** 基金名称 */
name: string;
/** 基金规模(亿元) */
scale: string;
}[];
/** 供暖单价(元/m³) */
heatingPrice: string;
/** 主键id */
id: number;
/** 轮播图 */
imgUrlList: string[];
/** 工业用电(元/㎡/天) */
industrialElectricity: string;
/** 工业用水单价(元/吨) */
industrialWaterPrice: string;
/** 总投资额(亿元) */
investmentAmountTotal: number;
/** 招商方向 */
investmentDetails: Investment[];
/** 亩均投资强度(单位:万/亩) */
investmentStrength: string;
/** 人力成本(元/月) */
laborCost: number;
/** 土地成本(万元/亩) */
landCost: number;
/** 土地分页信息 */
landPageList: {
/** 面积(亩) */
area: number;
/** */
cityCode: string;
/** */
cityName: string;
/** id */
id: number;
/** 首屏图片 */
imgUrl: string;
/** */
landType: number;
/** 地块名称 */
name: string;
/** 价格 */
price: number;
/** */
provinceCode: string;
/** */
provinceName: string;
/** */
regionCode: string;
/** */
regionName: string;
/** */
typeName: string;
}[];
/** 纬度(坐标系gcj02) */
latitude: number;
/** 开发区级别 */
levelCode: string;
/** 开发区级别名称 */
levelName: string;
/** 经度(坐标系gcj02) */
longitude: number;
/** 开发区名称 */
name: string;
/** 天然气单价(元/m³) */
naturalGasPrice: string;
/** 开发区内产业园列表 */
parkPageList: {
/** 城市code */
cityCode: string;
/** 城市 */
cityName: string;
/** 占地面积(单位:亩) */
coverArea: number;
/** 主键id */
id: number;
/** 图片链接 */
imgUrl: string;
/** 招商方向 */
investmentDirection: [];
/** 园区级别 */
levelCode: string;
/** 园区级别名称 */
levelName: string;
/** 园区名称 */
name: string;
/** 省份code */
provinceCode: string;
/** 省份 */
provinceName: string;
/** 区/县code */
regionCode: string;
/** 区/县 */
regionName: string;
/** 认证状态(1-未认证;2-认证中;3-已认证;4-已拒绝) */
status: AuthStatus;
/** 认证状态名称(1-未认证;2-认证中;3-已认证;4-已拒绝) */
statusName: string;
}[];
/** 规划面积(平方公里) */
planArea: number;
/** 产业政策详情 */
policyList: ListInfo<Policy>[];
/** 主导产业 */
primaryIndustry: string;
/** 禁限目录 */
prohibitedDirectory: string;
/** 省code */
provinceCode: string;
/** 省 */
provinceName: string;
/** 区/县code */
regionCode: string;
/** 区/县 */
regionName: string;
/** 污水处理价格(元/吨) */
sewageWaterPrice: number;
/** 污水处理类别 */
sewageWaterType: string;
/** 星级开发区附加信息 */
starObjectExtensionInfoMap: {
/** */
additionalProperties1: {
/** */
coverUrl: string;
/** */
title: string;
/** */
titleIntroduce: string;
};
};
/** 蒸汽价格(元/m³) */
steamPrice: string;
/** 变电站级别 */
substationLevel: string[];
/** 亩均税收(单位:万/亩) */
taxationStrength: string;
/** 开发区类型 */
type: string;
/** 能评要求(单位:tce/万元) */
valueAddedEnergy: string;
}
......@@ -58,4 +58,8 @@ export interface DevelopZoneItem {
statusName: string;
/** 开发区类型 */
type: string;
/** 星级开发区标识: 1-是 */
isSuper: number;
/** 星级开发区标识: 1-是 */
isSuperName: string;
}
import type { Investment, ListInfo, Policy } from '../common.ts';
/**
* 产业园详情
*/
......@@ -8,6 +10,8 @@ export interface IndustrialParkDetailResp {
averageOutputValue: string;
/** 建设周期(月) */
buildingPeriod: number;
/** 轮播图 */
imgUrlList: string[];
/** 城市code */
cityCode: string;
/** 城市 */
......@@ -31,7 +35,7 @@ export interface IndustrialParkDetailResp {
/** 总投资额(亿元) */
investmentAmountTotal: number;
/** 招商方向 */
investmentDetails: string;
investmentDetails: Investment[];
/** 投资强度(单位:万/亩) */
investmentStrength: string;
/** 园区级别 */
......@@ -56,15 +60,11 @@ export interface IndustrialParkDetailResp {
imgUrl: string;
/** 载体类型 */
type: string;
name: string;
id: number;
}[];
/** 产业政策 */
parkPolicy: {
type: string;
info: {
content: string;
type: string;
}[];
}[];
parkPolicy: ListInfo<Policy>[];
/** 主导产业 */
primaryIndustry: string;
/** 禁限目录 */
......
import type { Investment } from '../common.ts';
/**
* 土地详情
*/
......@@ -6,7 +8,7 @@ export interface LandDetailResp {
address: string;
/** */
addressDetails: string;
/** */
/** 出让年限 */
ageLimit: string;
/** 面积(亩) */
area: number;
......@@ -14,7 +16,7 @@ export interface LandDetailResp {
cityCode: string;
/** */
cityName: string;
/** */
/** 建设周期 */
constructionCycle: string;
/** */
contactInformation: string;
......@@ -22,7 +24,7 @@ export interface LandDetailResp {
contacterName: string;
/** 联系电话 */
contacterPhone: string;
/** */
/** 开发程度 */
developLevel: string;
/** 土地开发区信息 */
development: {
......@@ -52,10 +54,11 @@ export interface LandDetailResp {
regionName: string;
/** 类型 */
type: string;
id: number;
};
/** */
/** 能评要求 */
energyRequire: string;
/** */
/** 持有方类别 */
holderType: string;
/** 首屏图片 */
imgUrl: string;
......@@ -63,11 +66,11 @@ export interface LandDetailResp {
imgUrls: string[];
/** 产业方向 */
industryDirection: string;
/** */
/** 亩均投资强度 */
investNum: string;
/** 招商方向 */
investmentDetails: string;
/** */
investmentDetails: Investment[];
/** 土地证价格 */
landDeed: string;
/** 土地现状 */
landStatus: string;
......@@ -75,7 +78,7 @@ export interface LandDetailResp {
landType: number;
/** 地块名称 */
name: string;
/** */
/** 亩均年产值 */
outputValue: string;
/** 价格 */
price: number;
......@@ -83,17 +86,17 @@ export interface LandDetailResp {
provinceCode: string;
/** */
provinceName: string;
/** */
/** 容积率 */
ratio: string;
/** */
regionCode: string;
/** */
regionName: string;
/** */
/** 出让状态 */
shellState: string;
/** */
/** 亩均税收 */
taxRevenue: string;
/** */
/** 总投资额 */
totalInvestment: string;
/** 土地类型名称 */
typeName: string;
......
import type { ListOrder } from './enum';
import type { ListOrder } from './enum.ts';
/**
* 头部菜单项
......@@ -63,3 +63,40 @@ export type FilterListItem = {
value2?: number | string;
};
};
/**
* 列表属性信息如重点企业等
*/
export interface ListInfo<T> {
type: string;
info: T[];
}
/**
* 代表企业
*/
export interface Enterprise {
enterpriseName: string;
industryDirection: string;
track: string;
}
/**
* 产业政策
*/
export interface Policy {
content: string;
name: string;
type: string;
}
/**
* 招商方向
*/
export interface Investment {
/** 招商类型 */
code: number;
/** 招商类型名称 */
codeName: string;
/** 招商详情 */
details: string;
}
......@@ -25,3 +25,29 @@ export enum AuthStatus {
/** 已回退 */
back = 9,
}
/**
* 园区详情类型
*/
export enum ParkItemType {
/** 开发区 */
develop = 'develop',
/** 产业园 */
industrial = 'industrial',
}
/**
* 详情类型
*/
export enum DetailType {
/** 开发区 */
develop = 'develop',
/** 产业园 */
industrial = 'industrial',
/** 星级开发区 */
startDevelop = 'startDevelop',
/** 土地 */
land = 'land',
/** 载体 */
carrier = 'carrier',
}
type AddressItem = {
provinceName?: string;
cityName?: string;
regionName?: string;
[index: string]: any;
};
/**
* 有值不为空
*/
export function haveValue(i: unknown) {
return i === 0 || !!i;
}
/**
* 地址展示
*/
export function filterAddress(item: AddressItem) {
const { provinceName, cityName, regionName } = item;
return [provinceName, cityName, regionName].filter(haveValue).join('-');
}
<!-- 载体 -->
<template>
<div v-if="detail" class="main-width mx-auto py-5">
<div>{{ detail.name }}</div>
<div class="space-y-4">
<DetailInfoCell>
<DetailMain
:type="DetailType.carrier"
:detail="{ ...detail.carrierParkVo, ...detail, imgUrlList: detail.imgUrls }"
></DetailMain>
</DetailInfoCell>
<DetailInfoCell title="载体详情">
<DetailInfo :infos="carrierInfo"></DetailInfo>
</DetailInfoCell>
<DetailInfoCell title="入驻要求">
<DetailInfo :infos="joinRequired"></DetailInfo>
</DetailInfoCell>
<DetailInfoCell title="配套设施"></DetailInfoCell>
<DetailInfoCell title-center title="所属产业园">
<ParkItem
:type="ParkItemType.industrial"
:item="detail.carrierParkVo"
@to-detail="toIndustrial"
></ParkItem>
</DetailInfoCell>
</div>
</div>
</template>
<script lang="ts" setup>
import DetailInfo, { type Infos } from '@/components/detail/DetailInfo.vue';
import DetailInfoCell from '@/components/detail/DetailInfoCell.vue';
import DetailMain from '@/components/detail/DetailMain.vue';
import ParkItem from '@/components/list/ParkItem.vue';
import { useDetail } from '@/composable/useDetail.ts';
import { useJoinRequired } from '@/composable/useJoinRequired.ts';
import { useJump } from '@/composable/useJump.ts';
import { RouteName } from '@/router/router.ts';
import { RequestUrl } from '@/types/api.ts';
import type { CarrierDetailResp } from '@/types/api/carrierDetail';
import { DetailType, ParkItemType } from '@/types/enum.ts';
import { computed } from 'vue';
const { detail, initDetail } = useDetail<CarrierDetailResp>(RequestUrl.carrierDetail);
const { joinRequired } = useJoinRequired(
computed(() => {
if (detail.value) {
return {
...detail.value,
...detail.value.carrierParkVo,
};
} else {
return null;
}
}),
true,
);
const { open } = useJump();
/** 载体详情 */
const carrierInfo = computed(() => {
if (detail.value) {
const {
floorNumber,
floorHigh,
singleArea,
weight,
distance,
elevatorType,
fireProtectionLevel,
fireResistant,
} = detail.value;
const result: Infos = [
[
{
name: '载体层数',
value: floorNumber,
},
{
name: '层高',
value: floorHigh,
unit: 'm',
},
],
[
{
name: '单层面积',
value: singleArea,
unit: '㎡',
},
{
name: '承重',
value: weight,
unit: 'kg/㎡',
},
],
[
{
name: '柱距',
value: distance,
unit: 'm',
},
{
name: '电梯',
value: elevatorType,
},
],
[
{
name: '消防等级',
value: fireProtectionLevel,
},
{
name: '耐火等级',
value: fireResistant,
},
],
];
return result;
} else {
return [];
}
});
const toIndustrial = () => {
open({
query: {
id: detail.value?.carrierParkVo.id,
},
path: RouteName.industrialParkDetail,
});
};
initDetail();
</script>
<!-- 开发区详情 -->
<template>
<div v-if="detail">
<div v-if="detail" class="main-width mx-auto py-5">
<div>{{ detail.name }}</div>
<div class="space-y-4">
<DetailInfoCell>
<DetailMain></DetailMain>
</DetailInfoCell>
<DetailInfoCell title="开发区简介"></DetailInfoCell>
<DetailInfoCell title="产业情况"></DetailInfoCell>
<DetailInfoCell title="基本信息"></DetailInfoCell>
<DetailInfoCell title="要素成本"></DetailInfoCell>
<DetailInfoCell title="入驻要求"></DetailInfoCell>
<DetailInfoCell title="重点企业"></DetailInfoCell>
<DetailInfoCell title="产业政策"></DetailInfoCell>
<DetailInfoCell title="产业基金"></DetailInfoCell>
<DetailMain :type="DetailType.develop" :detail="detail"></DetailMain>
</DetailInfoCell>
<DetailInfoCell title="开发区简介">
<div class="font-yahei text-xs leading-5 text-[#1A1A1A]">
{{ detail.description }}
</div>
</DetailInfoCell>
<DetailInfoCell title="要素成本">
<DetailInfo :infos="elementCost"></DetailInfo>
</DetailInfoCell>
<DetailInfoCell title="重点企业">
<DetailInfoTab v-model="importantEnterpriseIndex" :tabs="importantEnterpriseTabs">
<DetailInfo :infos="importantEnterprise[importantEnterpriseIndex]"></DetailInfo>
</DetailInfoTab>
</DetailInfoCell>
<DetailInfoCell title="产业政策">
<DetailInfoTab v-model="industryPolicyIndex" :tabs="industryPolicyTabs">
<DetailInfo :infos="industryPolicy[industryPolicyIndex]"></DetailInfo>
</DetailInfoTab>
</DetailInfoCell>
<DetailInfoCell v-if="industryFund.length" title="产业基金">
<DetailInfo :infos="industryFund"></DetailInfo>
</DetailInfoCell>
<DetailInfoCell title="入驻要求">
<DetailInfo :infos="joinRequired"></DetailInfo>
</DetailInfoCell>
<DetailInfoCell title="配套设施"></DetailInfoCell>
<DetailInfoCell title="开发区内产业园" to-list-text="更多产业园"></DetailInfoCell>
<DetailInfoCell title="开发区内土地" to-list-text="更多土地"></DetailInfoCell>
<DetailInfoCell
v-if="industrials.length"
title-center
title="开发区内产业园"
to-list-text="查看更多"
@to-list="toIndustrialList"
>
<div class="flex space-x-[23px]">
<MiniListItem
v-for="item of industrials.slice(0, 4)"
:key="item.name"
:detail="item"
@to-detail="toIndustrialDetail(item)"
></MiniListItem>
</div>
</DetailInfoCell>
<DetailInfoCell
v-if="lands.length"
title-center
title="开发区内土地"
to-list-text="查看更多"
@to-list="toLandList"
>
<div class="flex space-x-[23px]">
<MiniListItem
v-for="item of lands.slice(0, 4)"
:key="item.name"
:detail="item"
@to-detail="toLandDetail(item)"
></MiniListItem>
</div>
</DetailInfoCell>
</div>
</div>
</template>
<script lang="ts" setup>
import DetailInfoCell from '@/components/detail/DetailInfoCell.vue';
import DetailInfo, { type Infos } from '@/components/detail/DetailInfo.vue';
import DetailMain from '@/components/detail/DetailMain.vue';
import { useDetail } from '@/composable/useDetail.ts';
import { RequestUrl } from '@/types/api.ts';
import type { DevelopZoneDetailResp } from '@/types/api/developZoneDetail';
import type { DevelopZoneDetailResp } from '@/types/api/developZoneDetail.ts';
import { computed } from 'vue';
import DetailInfoTab from '@/components/detail/DetailInfoTab.vue';
import { useInfoTab } from '@/composable/useInfoTab.ts';
import type { Enterprise, Policy } from '@/types/common';
import { useJoinRequired } from '@/composable/useJoinRequired.ts';
import { haveValue } from '@/utils/filters.ts';
import MiniListItem from '@/components/detail/MiniListItem.vue';
import { useJump } from '@/composable/useJump.ts';
import { RouteName } from '@/router/router.ts';
import { DetailType } from '@/types/enum.ts';
const { detail, initDetail } = useDetail<DevelopZoneDetailResp>(RequestUrl.developZoneDetail);
const { joinRequired } = useJoinRequired<DevelopZoneDetailResp>(computed(() => detail.value));
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { detail, detailId, initDetail } = useDetail<DevelopZoneDetailResp>(
RequestUrl.developZoneDetail,
/** 重点企业 */
const [importantEnterpriseIndex, importantEnterpriseTabs, importantEnterprise] =
useInfoTab<Enterprise>(
({ enterpriseName, track }) => {
return [
{
name: '企业名称',
value: enterpriseName,
},
{
name: '所属赛道',
value: track,
},
];
},
computed(() => detail.value?.enterpriseList || []),
);
/** 产业政策 */
const [industryPolicyIndex, industryPolicyTabs, industryPolicy] = useInfoTab<Policy>(
({ name, content }) => {
return [
{
name: '政策名称',
value: name,
},
{
name: '政策内容',
value: content,
},
];
},
computed(() => detail.value?.policyList || []),
);
const { open } = useJump();
/** 要素成本 */
const elementCost = computed(() => {
if (detail.value) {
const {
steamPrice,
naturalGasPrice,
laborCost,
landCost,
industrialElectricity,
industrialWaterPrice,
substationLevel,
sewageWaterPrice,
heatingPrice,
} = detail.value;
const result: Infos = [
[
{
name: '土地成本',
value: landCost,
unit: '万元/亩',
},
{
name: '人力成本',
value: laborCost,
unit: '元/月',
},
],
[
{
name: '工业用电',
value: industrialElectricity,
unit: '元/㎡/天',
},
{
name: '变电站级别',
value: substationLevel?.join?.(','),
},
],
[
{
name: '工业用水',
value: industrialWaterPrice,
unit: '元/吨',
},
{
name: '污水处理价格',
value: sewageWaterPrice,
unit: '元/吨',
},
],
[
{
name: '天然气单价',
value: naturalGasPrice,
unit: '元/m³',
},
{
name: '蒸汽单价',
value: steamPrice,
unit: '元/m³',
},
],
[
{
name: '供暧单价',
value: heatingPrice,
unit: '元/m³',
},
],
];
return result;
} else {
return [];
}
});
/** 产业基金 */
const industryFund = computed(() => {
if (detail.value) {
const result: Infos = detail.value.fundList
.map(({ investmentDirection, name, scale }) => {
return [
[
{
name: '基金名称',
value: name,
},
{
name: '基金规模',
value: scale,
},
],
[
{
name: '投资方向',
value: investmentDirection,
oneline: true,
},
],
];
})
.flat();
return result;
} else {
return [];
}
});
/** 产业园列表 */
const industrials = computed(() => {
if (detail.value) {
return detail.value.parkPageList.map(({ imgUrl, name, levelName, coverArea, id }) => {
return {
imgUrl,
name,
info: [levelName, coverArea ? `${coverArea}㎡` : ''].filter(haveValue).join(' | '),
id,
};
});
} else {
return [];
}
});
/** 开发区列表 */
const lands = computed(() => {
if (detail.value) {
return detail.value.landPageList.map(({ name, imgUrl, area, typeName, id }) => {
return {
imgUrl,
name,
info: [area ? `${area}亩` : '', typeName].filter(haveValue).join(' | '),
id,
};
});
} else {
return [];
}
});
const toIndustrialList = () => {
open(RouteName.industrialParkList);
};
const toLandList = () => {
open(RouteName.landList);
};
const toIndustrialDetail = ({ id }: { id: string | number }) => {
open({
name: RouteName.industrialParkDetail,
query: {
id,
},
});
};
const toLandDetail = ({ id }: { id: string | number }) => {
open({
name: RouteName.landDetail,
query: {
id,
},
});
};
initDetail();
</script>
<!-- 开发区列表 -->
<template>
<div>
<div>
<div
<div class="main-width mx-auto">
<div class="space-y-5">
<ParkItem
v-for="item in datas"
:key="item.id"
@click="handleDetail(RouteName.developZoneDetail, item)"
>
<div>{{ item.name }}</div>
</div>
:item="item"
:type="ParkItemType.develop"
@to-detail="handleDetail(RouteName.developZoneDetail, item)"
></ParkItem>
<ListPagination
v-model:current-page="pageNum"
......@@ -23,9 +23,10 @@
<script lang="ts" setup>
import { useListView } from '@/composable/useListView.ts';
import { RouteName } from '@/router/router.ts';
import ListPagination from '@/components/list/list-pagination.vue';
import ListPagination from '@/components/list/ListPagination.vue';
import { RequestUrl } from '@/types/api.ts';
import type { DevelopZoneItem, DevelopZoneListCondition } from '@/types/api/developZoneList';
import { ParkItemType } from '@/types/enum.ts';
const { pageNum, pageSize, totalCount, datas, handleDetail } = useListView<
DevelopZoneItem,
......
<!-- 首页 -->
<template>
<ElForm :model="form" label-width="120px">
<ElFormItem label="Activity name" class="w-2 px-4">
<ElInput v-model="form.name" />
</ElFormItem>
<ElFormItem label="Activity zone">
<ElSelect v-model="form.region" placeholder="please select your zone">
<ElOption label="Zone one" value="shanghai" />
<ElOption label="Zone two" value="beijing" />
</ElSelect>
</ElFormItem>
<ElFormItem label="Activity time">
<ElCol :span="11">
<ElDatePicker
v-model="form.date1"
type="date"
placeholder="Pick a date"
style="width: 100%"
/>
</ElCol>
<ElCol :span="2" class="text-center">
<span class="text-gray-500">-</span>
</ElCol>
<ElCol :span="11">
<ElTimePicker v-model="form.date2" placeholder="Pick a time" style="width: 100%" />
</ElCol>
</ElFormItem>
<ElFormItem label="Instant delivery">
<ElSwitch v-model="form.delivery" />
</ElFormItem>
<ElFormItem label="Activity type">
<ElCheckboxGroup v-model="form.type">
<ElCheckbox label="Online activities" name="type" />
<ElCheckbox label="Promotion activities" name="type" />
<ElCheckbox label="Offline activities" name="type" />
<ElCheckbox label="Simple brand exposure" name="type" />
</ElCheckboxGroup>
</ElFormItem>
<ElFormItem label="Resources">
<ElRadioGroup v-model="form.resource">
<ElRadio label="Sponsor" />
<ElRadio label="Venue" />
</ElRadioGroup>
</ElFormItem>
<ElFormItem label="Activity form">
<ElInput v-model="form.desc" type="textarea" />
</ElFormItem>
<ElFormItem>
<ElButton type="primary" @click="onSubmit">Create</ElButton>
<ElButton>Cancel</ElButton>
</ElFormItem>
</ElForm>
<div>
<div class="header-bg flex h-[522px] w-full items-center justify-center">
<div class="header-bg__img h-[428px] w-[1129px] px-[82px] pb-[68px] pt-[85px]">
<div class="flex justify-between">
<div>
<div
class="header-bg__content font-yahei text-5xl font-bold leading-[56px] tracking-[2px] text-[#C0322B]"
>
懂产业的企业选址平台
</div>
</div>
<div class="mt-[25px]">
<img class="h-auto w-[312px]" src="@/assets/images/home-header-right.png" />
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import { cloneDeep } from 'lodash-es';
const form = reactive({
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: cloneDeep([]),
resource: '',
desc: '',
});
const onSubmit = () => {
console.log('submit!');
};
</script>
<script lang="ts" setup></script>
<style lang="scss">
.test-a {
position: absolute;
top: 1px;
display: flex;
.header-bg {
background: linear-gradient(160deg, #ffffff 0%, #f7f7f7 27%, #e1e1e1 68%, #cccccc 100%) no-repeat;
&__img {
background: url('@/assets/images//home-header-bg.png') no-repeat;
background-size: contain;
}
&__content {
text-shadow: 0px 14px 42px rgba(90, 0, 0, 0.12);
}
}
</style>
<!-- 产业园详情 -->
<template>
<div v-if="detail">
<div>{{ detail.name }}</div>
<div v-if="detail" class="main-width mx-auto py-5">
<div class="space-y-4">
<DetailInfoCell>
<DetailMain></DetailMain>
<DetailMain :type="DetailType.industrial" :detail="detail"></DetailMain>
</DetailInfoCell>
<DetailInfoCell title="产业园简介">
<div class="font-yahei text-xs leading-5 text-[#1A1A1A]">
{{ detail.description }}
</div>
</DetailInfoCell>
<DetailInfoCell title="产业基础">
<DetailInfo :infos="industryBase"></DetailInfo>
</DetailInfoCell>
<DetailInfoCell title="入驻要求">
<DetailInfo :infos="joinRequired"></DetailInfo>
</DetailInfoCell>
<DetailInfoCell title="要素成本">
<DetailInfo :infos="elementCost"></DetailInfo>
</DetailInfoCell>
<DetailInfoCell v-if="industrySupports.length" title="产业支持">
<div class="flex flex-wrap gap-x-2">
<div
v-for="item of industrySupports"
:key="item"
class="flex h-8 items-center px-5 font-yahei text-sm leading-[22px] text-[#4D4D4D]"
>
{{ item }}
</div>
</div>
</DetailInfoCell>
<DetailInfoCell title="产业政策">
<DetailInfoTab v-model="industryPolicyIndex" :tabs="industryPolicyTabs">
<DetailInfo :infos="industryPolicy[industryPolicyIndex]"></DetailInfo>
</DetailInfoTab>
</DetailInfoCell>
<DetailInfoCell v-if="enterprises.length" title="代表企业">
<div class="flex flex-wrap gap-x-2">
<div
v-for="item of enterprises"
:key="item"
class="flex h-8 items-center px-5 font-yahei text-sm leading-[22px] text-[#4D4D4D]"
>
{{ item }}
</div>
</div>
</DetailInfoCell>
<DetailInfoCell v-if="detail.setUp.length" title="园内配套信息">
<div class="flex flex-wrap gap-x-5">
<div
v-for="item of detail.setUp || []"
:key="item"
class="flex h-[30px] items-center rounded-sm px-[14px] font-yahei text-sm leading-[22px] text-[#4D4D4D] outline outline-1 outline-[#E6E6E6]"
>
{{ item }}
</div>
</div>
</DetailInfoCell>
<DetailInfoCell title="配套设施"></DetailInfoCell>
<DetailInfoCell
v-if="carriers.length"
title-center
title="产业园内载体"
to-list-text="查看更多"
@to-list="toCarrierList"
>
<div class="flex space-x-[23px]">
<MiniListItem
v-for="item of carriers.slice(0, 4)"
:key="item.name"
:detail="item"
@to-detail="toCarrierDetail(item)"
></MiniListItem>
</div>
</DetailInfoCell>
<DetailInfoCell title="产业园简介"></DetailInfoCell>
<DetailInfoCell title="入驻要求"></DetailInfoCell>
<DetailInfoCell title="要素成本"></DetailInfoCell>
<DetailInfoCell title="产业支持"></DetailInfoCell>
<DetailInfoCell title="产业政策"></DetailInfoCell>
<DetailInfoCell title="代表企业"></DetailInfoCell>
<DetailInfoCell title="园内配套信息"></DetailInfoCell>
<DetailInfoCell title="周边配套"></DetailInfoCell>
<DetailInfoCell title="产业园内载体" to-list-text="更多产业园区"></DetailInfoCell>
</div>
</div>
</template>
<script lang="ts" setup>
import DetailInfo, { type Info, type Infos } from '@/components/detail/DetailInfo.vue';
import DetailInfoCell from '@/components/detail/DetailInfoCell.vue';
import DetailMain from '@/components/detail/DetailMain.vue';
import { useDetail } from '@/composable/useDetail.ts';
import { useInfoTab } from '@/composable/useInfoTab.ts';
import { useJoinRequired } from '@/composable/useJoinRequired.ts';
import { RequestUrl } from '@/types/api.ts';
import type { IndustrialParkDetailResp } from '@/types/api/industrialParkDetail';
import type { Policy } from '@/types/common';
import { computed } from 'vue';
import DetailInfoTab from '@/components/detail/DetailInfoTab.vue';
import MiniListItem, { type MiniDetail } from '@/components/detail/MiniListItem.vue';
import { haveValue } from '@/utils/filters.ts';
import { useJump } from '@/composable/useJump.ts';
import { RouteName } from '@/router/router.ts';
import { DetailType } from '@/types/enum.ts';
const { detail, initDetail } = useDetail<IndustrialParkDetailResp>(RequestUrl.industrialParkDetal);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { detail, detailId, initDetail } = useDetail<IndustrialParkDetailResp>(
RequestUrl.industrialParkDetal,
const { joinRequired } = useJoinRequired<IndustrialParkDetailResp>(computed(() => detail.value));
/** 产业政策 */
const [industryPolicyIndex, industryPolicyTabs, industryPolicy] = useInfoTab<Policy>(
({ name, content }) => {
return [
{
name: '政策名称',
value: name,
},
{
name: '政策内容',
value: content,
},
];
},
computed(() => detail.value?.parkPolicy || []),
);
const { open } = useJump();
/** 产业基础 */
const industryBase = computed(() => {
if (detail.value) {
const { investmentDetails, primaryIndustry } = detail.value;
const result: Infos = [
[
{
name: '主导产业',
value: primaryIndustry,
oneline: true,
},
],
...(investmentDetails?.map<Info[]>(({ details }) => [
{
name: '招商方向',
value: details,
oneline: true,
},
]) || []),
];
return result;
} else {
return [];
}
});
/** 要素成本 */
const elementCost = computed(() => {
if (detail.value) {
const {
rentPrice,
salePrice,
propertyPrice,
substationLevel,
electricityPrice,
industrialWater,
gas,
lifeSewagePrice,
nresidentsHeating,
steam,
} = detail.value;
const result: Infos = [
[
{
name: '出租单价',
value: rentPrice,
unit: '元/㎡/月',
},
{
name: '出售单价',
value: salePrice,
unit: '元/㎡/月',
},
],
[
{
name: '物业费单价',
value: propertyPrice,
unit: '元/㎡/天',
},
{
name: '工业用电',
value: electricityPrice,
unit: '元/度',
},
],
[
{
name: '工业用水',
value: industrialWater,
unit: '元/吨',
},
{
name: '变电站级别',
value: substationLevel,
},
],
[
{
name: '污水处理价格',
value: lifeSewagePrice,
unit: '元/吨',
},
{
name: '天然气单价',
value: gas,
unit: '元/m³',
},
],
[
{
name: '蒸汽单价',
value: steam,
unit: '元/m³',
},
{
name: '供暧单价',
value: nresidentsHeating,
unit: '元/m³',
},
],
];
return result;
} else {
return [];
}
});
/** 产业支持 */
const industrySupports = computed(() => {
return detail.value?.resources || [];
});
/** 代表企业 */
const enterprises = computed(() => {
return detail.value?.enterprise.split(',') || [];
});
/** 产业园内载体列表 */
const carriers = computed<MiniDetail[]>(() => {
if (detail.value) {
return detail.value.parkCarrier.map(({ imgUrl, type, area, name, id }) => {
return {
imgUrl,
name,
info: [area ? `${area}㎡` : '', type].filter(haveValue).join(' | '),
id,
};
});
} else {
return [];
}
});
const toCarrierList = () => {
open(RouteName.carrierList);
};
/**
* 载体详情
*/
const toCarrierDetail = ({ id }: { id: number }) => {
open({
name: RouteName.carrierDetail,
query: {
id,
},
});
};
initDetail();
</script>
<!-- 产业园列表 -->
<template>
<div class="main-width mx-auto">
<div>
<div>
<div
<div class="space-y-5">
<ParkItem
v-for="item in datas"
:key="item.id"
@click="handleDetail(RouteName.industrialParkDetail, item)"
>
<div>{{ item.name }}</div>
:item="item"
:type="ParkItemType.industrial"
@to-detail="handleDetail(RouteName.industrialParkDetail, item)"
></ParkItem>
</div>
<ListPagination
v-model:current-page="pageNum"
v-model:page-size="pageSize"
class="mt-8"
:total="totalCount"
></ListPagination>
</div>
......@@ -23,14 +26,16 @@
import { useListView } from '@/composable/useListView.ts';
import { RouteName } from '@/router/router.ts';
import { RequestUrl } from '@/types/api.ts';
import ListPagination from '@/components/list/list-pagination.vue';
import ListPagination from '@/components/list/ListPagination.vue';
import ParkItem from '@/components/list/ParkItem.vue';
import type {
IndustrialParkItem,
IndustrialParkListCondition,
} from '@/types/api/industrialParkList';
} from '@/types/api/industrialParkList.ts';
import { ParkItemType } from '@/types/enum.ts';
const { pageNum, pageSize, totalCount, datas, handleDetail } = useListView<
IndustrialParkItem,
IndustrialParkListCondition
>(RequestUrl.developZoneList);
>(RequestUrl.industrialParkList);
</script>
<!-- 土地详情 -->
<template>
<div v-if="detail">
<div v-if="detail" class="main-width mx-auto py-5">
<div>{{ detail.name }}</div>
<div class="space-y-4">
<DetailInfoCell>
<DetailMain></DetailMain>
<DetailMain
:type="DetailType.land"
:detail="{ ...detail, imgUrlList: detail.imgUrls }"
></DetailMain>
</DetailInfoCell>
<DetailInfoCell title="土地现状">
<div class="font-yahei text-xs leading-5 text-[#1A1A1A]">
{{ detail.landStatus }}
</div>
</DetailInfoCell>
<DetailInfoCell title="地块信息">
<DetailInfo :infos="landInfo"></DetailInfo>
</DetailInfoCell>
<DetailInfoCell title="入驻要求">
<DetailInfo :infos="joinRequired"></DetailInfo>
</DetailInfoCell>
<DetailInfoCell title="土地现状"></DetailInfoCell>
<DetailInfoCell title="地块信息"></DetailInfoCell>
<DetailInfoCell title="入驻要求"></DetailInfoCell>
<DetailInfoCell title="周边配套"></DetailInfoCell>
<DetailInfoCell title="周边配套"></DetailInfoCell>
<DetailInfoCell title="所属开发区"></DetailInfoCell>
<DetailInfoCell title-center title="所属开发区">
<ParkItem
:type="ParkItemType.develop"
:item="detail.development"
@to-detail="toDevelop"
></ParkItem>
</DetailInfoCell>
</div>
</div>
</template>
<script lang="ts" setup>
import DetailInfoCell from '@/components/detail/DetailInfoCell.vue';
import DetailInfo from '@/components/detail/DetailInfo.vue';
import DetailMain from '@/components/detail/DetailMain.vue';
import { useDetail } from '@/composable/useDetail.ts';
import { useJoinRequired, type JoinRequiredParams } from '@/composable/useJoinRequired.ts';
import { RequestUrl } from '@/types/api.ts';
import type { LandDetailResp } from '@/types/api/landDetail';
import { computed } from 'vue';
import { DetailType, ParkItemType } from '@/types/enum.ts';
import { useJump } from '@/composable/useJump.ts';
import { RouteName } from '@/router/router.ts';
const { detail, initDetail } = useDetail<LandDetailResp>(RequestUrl.landDetail);
const { joinRequired } = useJoinRequired(
computed(() => {
if (detail.value) {
const {
energyRequire,
totalInvestment,
investNum,
outputValue,
taxRevenue,
constructionCycle,
} = detail.value;
const result: JoinRequiredParams = {
...detail.value,
valueAddedEnergy: energyRequire,
investmentAmountTotal: totalInvestment,
investmentStrength: investNum,
averageOutputValue: outputValue,
taxationStrength: taxRevenue,
buildingPeriod: constructionCycle,
};
return result;
} else {
return null;
}
}),
true,
);
const { open } = useJump();
/** 地块信息 */
const landInfo = computed(() => {
if (detail.value) {
const { ratio, landDeed, ageLimit, developLevel, shellState, holderType } = detail.value;
return [
[
{
name: '容积率',
value: ratio,
},
{
name: '土地证',
value: landDeed,
},
],
[
{
name: '出让年限',
value: ageLimit,
},
{
name: '开发程度',
value: developLevel,
},
],
[
{
name: '出让状态',
value: shellState,
},
{
name: '持有方类别',
value: holderType,
},
],
];
} else {
return [];
}
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { detail, detailId, initDetail } = useDetail<LandDetailResp>(RequestUrl.landDetail);
/**
* 开发区
*/
const toDevelop = () => {
open({
name: RouteName.developZoneDetail,
query: {
id: detail.value?.development.id,
},
});
};
initDetail();
</script>
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-recommended-scss',
'stylelint-config-rational-order',
'stylelint-config-recommended-vue',
],
rules: {
'at-rule-no-unknown': [
true,
{
ignoreAtRules: ['tailwind', 'apply', 'variants', 'responsive', 'screen', 'layer'],
},
],
'no-descending-specificity': null,
'media-feature-range-notation': null,
'import-notation': null,
},
};
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {
fontFamily: {
yahei: '"Microsoft YaHei", system-ui, sans-serif',
},
},
},
};
......@@ -6,7 +6,6 @@ import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
// @ts-ignore
import eslint from 'vite-plugin-eslint';
import stylelint from 'vite-plugin-stylelint';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import legacy from '@vitejs/plugin-legacy';
......@@ -20,11 +19,6 @@ export default defineConfig({
eslint({
cache: true,
}),
stylelint({
lintInWorker: true,
cache: true,
cacheLocation: './node_modules/.vite/.stylelintcache',
}),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment