提交 f6dcc485 authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 5ce3cbc7
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
<meta name="theme-color" content="#3276fc" /> <meta name="theme-color" content="#3276fc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>紫荆教育e-SAAS平台</title> <title>紫荆教育e-SAAS平台</title>
<script src="https://webapp-pub.ezijing.com/plugins/tinymce@6/tinymce.min.js"></script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
......
差异被折叠。
...@@ -13,7 +13,9 @@ ...@@ -13,7 +13,9 @@
"deploy": "node ./deploy.js" "deploy": "node ./deploy.js"
}, },
"dependencies": { "dependencies": {
"@tinymce/tinymce-vue": "^5.0.0",
"axios": "^0.27.2", "axios": "^0.27.2",
"blueimp-md5": "^2.19.0",
"countup.js": "^2.2.0", "countup.js": "^2.2.0",
"element-plus": "^2.2.5", "element-plus": "^2.2.5",
"pinia": "^2.0.14", "pinia": "^2.0.14",
...@@ -22,6 +24,7 @@ ...@@ -22,6 +24,7 @@
}, },
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.1.3", "@rushstack/eslint-patch": "^1.1.3",
"@types/blueimp-md5": "^2.18.0",
"@types/node": "^17.0.43", "@types/node": "^17.0.43",
"@vitejs/plugin-vue": "^2.3.3", "@vitejs/plugin-vue": "^2.3.3",
"@vue/eslint-config-typescript": "^11.0.0", "@vue/eslint-config-typescript": "^11.0.0",
...@@ -35,6 +38,6 @@ ...@@ -35,6 +38,6 @@
"unplugin-auto-import": "^0.8.8", "unplugin-auto-import": "^0.8.8",
"vite": "^2.9.12", "vite": "^2.9.12",
"vite-plugin-checker": "^0.4.6", "vite-plugin-checker": "^0.4.6",
"vue-tsc": "^0.37.8" "vue-tsc": "^0.37.9"
} }
} }
...@@ -21,9 +21,11 @@ export function getSignature() { ...@@ -21,9 +21,11 @@ export function getSignature() {
} }
// 图片上传 // 图片上传
export function uploadFile(data: object) { export function uploadFile(data: Record<string, any>) {
return httpRequest.post('https://webapp-pub.oss-cn-beijing.aliyuncs.com', data, { return httpRequest
withCredentials: false, .post('https://webapp-pub.oss-cn-beijing.aliyuncs.com', data, {
headers: { 'Content-Type': 'multipart/form-data' } withCredentials: false,
}) headers: { 'Content-Type': 'multipart/form-data' }
})
.then(() => data)
} }
...@@ -86,3 +86,11 @@ textarea:focus { ...@@ -86,3 +86,11 @@ textarea:focus {
body { body {
font-size: 14px; font-size: 14px;
} }
.el-form-item__label {
--el-form-label-font-size: 16px;
--el-text-color-regular: #333;
}
.el-button.is-link {
--el-button-text-color: #399ee8;
}
// styles/element/index.scss
/* 只需要重写你需要的即可 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': #aa1941
)
)
);
// 如果只是按需导入,则可以忽略以下内容。
// 如果你想导入所有样式:
@use 'element-plus/theme-chalk/src/index.scss' as *;
<script setup lang="ts">
interface MenuType {
title: string
path: string
}
const props = defineProps<{
menus?: MenuType[]
background?: string
}>()
const route = useRoute()
const padding = computed(() => {
return props.background ? '20px' : 0
})
function genMenuClassName(item: MenuType) {
return route.fullPath.includes(item.path) ? 'is-active' : ''
}
</script>
<template>
<div class="app-container">
<div class="app-container-aside" v-if="menus">
<ul>
<li v-for="(item, index) in menus" :key="index" :class="genMenuClassName(item)">
<router-link :to="item.path">{{ item.title }}</router-link>
</li>
</ul>
</div>
<div class="app-container-main"><slot></slot></div>
</div>
</template>
<style lang="scss">
.app-container {
padding: 40px 0 30px;
display: flex;
align-items: flex-start;
}
.app-container-aside {
flex: 0 0 110px;
margin-right: 20px;
background-color: v-bind(background);
border-radius: 20px;
li {
margin: 40px 0;
font-size: 16px;
line-height: 1;
color: #777;
text-align: center;
&.is-active {
color: #aa1941;
}
}
}
.app-container-main {
flex: 1;
padding: v-bind(padding);
background-color: v-bind(background);
border-radius: 20px;
}
</style>
<script setup lang="ts">
import { Search, RefreshLeft } from '@element-plus/icons-vue'
interface IRemoteProps {
params?: any
httpRequest?: any
beforeRequest?: any
callback?: any
}
const props = withDefaults(
defineProps<{
remote?: IRemoteProps
filters?: any[]
moreFilters?: any[]
columns: any[]
data?: any[]
hasPagination?: boolean
limit?: number
}>(),
{
hasPagination: true,
limit: 10,
data() {
return []
}
}
)
const filterFormRef = ref()
const loading = ref(false)
const tableRef = ref()
const dataList = ref<any[]>([])
const page = reactive({ total: 0, size: props.limit, currentPage: 1 })
const params = reactive({ ...props.remote?.params })
watch(
() => props.data,
list => {
dataList.value = list || []
},
{ immediate: true }
)
// 获取数据
const fetchList = (isReset = false) => {
/**
* @param function httpRequest api接口
* @param function beforeRequest 接口请求之前
* @param function callback 接口请求成功回调
*/
const { httpRequest, beforeRequest, callback } = props.remote || {}
if (!httpRequest) {
return
}
// 参数设置
let requestParams = { ...params }
// 翻页参数设置
if (props.hasPagination) {
requestParams.page = page.currentPage
requestParams['per-page'] = page.size
}
// 接口请求之前
if (beforeRequest) {
requestParams = beforeRequest(requestParams, isReset)
}
// for (const key in params) {
// if (params[key] === '' || params[key] === undefined || params[key] === undefined) {
// delete params[key]
// }
// }
loading.value = true
return (
httpRequest(requestParams)
.then((res: any) => {
const { list = [], total = 0 } = callback ? callback(res.data, requestParams) : res.data || {}
page.total = total
dataList.value = list
})
// .catch(() => {
// page.total = 0
// dataList.value = []
// })
.finally(() => {
loading.value = false
})
)
}
// 搜索
const search = () => {
page.currentPage = 1
return fetchList()
}
// 重置
const reset = () => {
// 清空筛选条件
filterFormRef.value?.resetFields()
// 初始化页码
page.currentPage = 1
// 刷新列表
return fetchList(true)
}
// 刷新
const refetch = (isForce = false) => {
return isForce ? reset() : fetchList()
}
// 页数改变
const pageSizeChange = (value: number) => {
page.currentPage = 1
page.size = value
fetchList()
}
onMounted(() => {
fetchList()
})
defineExpose({ refetch, tableRef })
</script>
<template>
<div class="table-list">
<div class="table-list-hd">
<!-- 筛选 -->
<div class="table-list-filter" v-if="filters && filters.length">
<el-form :inline="true" :model="params" ref="filterFormRef" @submit.prevent>
<template v-for="item in filters" :key="item.prop">
<el-form-item :label="item.label" :prop="item.prop">
<template v-if="item.slots">
<slot :name="item.slots" v-bind="{ params }"></slot>
</template>
<template v-else>
<!-- input -->
<el-input
v-model="params[item.prop]"
v-bind="item"
clearable
@change="search"
style="width: 200px"
v-if="item.type === 'input'"
/>
<!-- select -->
<el-select
v-model="params[item.prop]"
v-bind="item"
clearable
@change="search"
style="width: 200px"
v-if="item.type === 'select'"
>
<el-option
:label="option[item.labelKey] || option.label"
:value="option[item.valueKey] || option.value"
v-for="(option, index) in item.options"
:key="index"
/>
</el-select>
</template>
</el-form-item>
</template>
<el-form-item class="filter-buttons">
<el-button type="primary" :icon="Search" @click="search">搜索</el-button>
<el-button :icon="RefreshLeft" @click="reset">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="table-list-hd-aside"><slot name="header-aside" /></div>
</div>
<slot></slot>
<!-- 主体 -->
<div class="table-list-bd">
<slot name="body" v-bind="{ data: dataList }">
<el-table
stripe
:header-cell-style="{ background: '#ededed', fontSize: '16px', fontWeight: 'normal', color: '#4d4d4d' }"
:data="dataList"
v-loading="loading"
v-bind="$attrs"
style="height: 100%"
ref="tableRef"
>
<el-table-column v-bind="item" v-for="item in columns" :key="item.prop">
<template #default="scope" v-if="item.slots || item.computed">
<slot :name="item.slots" v-bind="scope" v-if="item.slots"></slot>
<div v-html="item.computed(scope)" v-if="item.computed"></div>
</template>
</el-table-column>
</el-table>
</slot>
</div>
<!-- 底部 -->
<div class="table-list-ft">
<div>
<slot name="footer"></slot>
</div>
<el-pagination
class="table-list-pagination"
background
layout="total, sizes, prev, pager, next, jumper"
:page-sizes="[10, 20, 30, 50, 100]"
:page-size="page.size"
:total="page.total"
v-model:currentPage="page.currentPage"
@size-change="pageSizeChange"
@current-change="fetchList()"
:hide-on-single-page="true"
v-if="hasPagination"
>
</el-pagination>
</div>
</div>
</template>
<style lang="scss">
// .table-list {
// height: 100%;
// display: flex;
// flex-direction: column;
// box-sizing: border-box;
// }
.table-list-hd {
display: flex;
margin-bottom: 20px;
}
.table-list-filter {
flex: 1;
}
// .table-list-bd {
// flex: 1;
// }
.table-list-ft {
padding: 10px 0;
display: flex;
align-items: center;
justify-content: space-between;
}
.table-list-pagination {
text-align: right;
}
.el-table-column--selection .cell {
padding: 0 14px !important;
}
</style>
<script lang="ts" setup>
import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import type { UploadProps, UploadUserFile } from 'element-plus'
import md5 from 'blueimp-md5'
import { getSignature } from '@/api/base'
const props = withDefaults(defineProps<{ modelValue: string | []; prefix?: string }>(), {
prefix: 'upload/admin/'
})
const emit = defineEmits(['update:modelValue'])
const uploadData = ref()
const fileList = ref<UploadUserFile[]>([])
watch(
() => props.modelValue,
value => {
fileList.value = Array.isArray(value) ? [...value] : []
}
)
const showFileList = computed(() => {
return Array.isArray(props.modelValue)
})
// 上传之前
const handleBeforeUpload = async (file: any) => {
const fileName = file.name
const key = props.prefix + md5(fileName + new Date().getTime()) + fileName.substr(fileName.lastIndexOf('.'))
const response: Record<string, any> = await getSignature()
uploadData.value = {
key,
OSSAccessKeyId: response.accessid,
policy: response.policy,
signature: response.signature,
success_action_status: '200',
url: `${response.host}/${key}`
}
file.url = `${response.host}/${key}`
}
// 上传成功
const handleSuccess = (response: any, file: any, files: any) => {
if (!files.every((item: any) => item.status === 'success')) return
if (showFileList.value) {
emit(
'update:modelValue',
files.map((item: any) => {
return { name: item.name, url: item.url || item.raw.url }
})
)
} else {
emit('update:modelValue', file.raw.url)
}
}
// 上传限制
const handleExceed: UploadProps['onExceed'] = () => {
ElMessage.warning('文件超出个数限制')
}
// 删除
const handleRemove: UploadProps['onRemove'] = (file, files) => {
emit(
'update:modelValue',
files.map((item: any) => {
return { name: item.name, url: item.url || item.raw.url }
})
)
}
// 预览
const handlePreview: UploadProps['onPreview'] = uploadFile => {
console.log(uploadFile)
}
</script>
<template>
<el-upload
action="https://webapp-pub.oss-cn-beijing.aliyuncs.com"
:data="uploadData"
:show-file-list="showFileList"
:before-upload="handleBeforeUpload"
:on-exceed="handleExceed"
:on-remove="handleRemove"
:on-preview="handlePreview"
:on-success="handleSuccess"
:file-list="fileList"
class="uploader"
>
<template v-if="showFileList">
<template v-if="$attrs['list-type'] === 'picture-card'">
<el-icon><Plus /></el-icon>
</template>
<template v-else>
<el-button type="primary">点击上传</el-button>
</template>
</template>
<div class="avatar-uploader" v-else>
<el-image :src="(modelValue as string)" fit="contain" v-if="modelValue" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</div>
<template #tip>
<div class="el-upload__tip"><slot name="tip"></slot></div>
</template>
</el-upload>
</template>
<style lang="scss">
.uploader {
flex: 1;
}
.avatar-uploader {
width: 146px;
height: 120px;
background: #f8f8f8;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
.el-image {
width: 100%;
height: 100%;
}
}
.avatar-uploader-icon {
font-size: 30px;
color: #666;
width: 100%;
height: 100%;
text-align: center;
}
</style>
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
<div class="app-layout"> <div class="app-layout">
<AppHeader></AppHeader> <AppHeader></AppHeader>
<section class="app-layout-container"> <section class="app-layout-container">
<router-view></router-view> <div class="app-layout-inner">
<router-view></router-view>
</div>
</section> </section>
<AppFooter></AppFooter> <AppFooter></AppFooter>
</div> </div>
...@@ -12,3 +14,15 @@ ...@@ -12,3 +14,15 @@
import AppHeader from './Header.vue' import AppHeader from './Header.vue'
import AppFooter from './Footer.vue' import AppFooter from './Footer.vue'
</script> </script>
<style>
.app-layout-container {
background: #f9f8f8 url('https://webapp-pub.ezijing.com/project/saas/bg.png') no-repeat center bottom;
background-size: cover;
background-attachment: fixed;
}
.app-layout-inner {
width: 1200px;
margin: 0 auto;
}
</style>
<script setup lang="ts">
import Editor from '@tinymce/tinymce-vue'
import md5 from 'blueimp-md5'
import { getSignature, uploadFile } from '@/api/base'
const ImageUploadHandler = (blobInfo: any) =>
new Promise((resolve, reject) => {
const file = blobInfo.blob()
getSignature()
.then((response: any) => {
const prefix = 'upload/admin/'
const key = prefix + md5(file.name + new Date().getTime()) + file.name.substr(file.name.lastIndexOf('.'))
const { accessid, policy, signature, host } = response
const params = {
key,
OSSAccessKeyId: accessid,
policy,
signature,
success_action_status: '200',
file,
url: `${host}/${key}`
}
uploadFile(params)
.then((res: any) => {
resolve(res.url)
})
.catch(() => {
reject('上传失败')
})
})
.catch(() => {
reject('获取Signature失败')
})
})
const init = {
language: 'zh-Hans',
height: 600,
menubar: false,
statusbar: false,
plugins: 'table charmap fullscreen lists link code preview quickbars',
toolbar:
'undo redo | fontsizeselect lineheight bold italic underline strikethrough forecolor backcolor | link quickimage image media table | align hangingindent indent outdent numlist bullist | charmap blockquote hr fullscreen | code preview',
// font_formats:
// '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Times New Roman',
fontsize_formats: '8px 10px 12px 14px 15px 16px 17px 18px 20px 24px',
lineheight_formats: '0.5 1 1.2 1.5 2',
images_upload_handler: ImageUploadHandler,
automatic_uploads: true,
quickbars_insert_toolbar: false,
// style_formats: [{ title: '悬挂缩进', block: 'p', styles: { textIndent: '-2em', paddingLeft: '2em' } }],
content_style: 'img {max-width:100%;}'
}
</script>
<template>
<editor :init="init" v-bind="$attrs" style="width: 100%" />
</template>
<style>
.tox-tinymce {
border: 1px solid var(--el-border-color) !important;
border-radius: var(--el-border-radius-base) !important;
}
</style>
...@@ -5,7 +5,13 @@ import App from './App.vue' ...@@ -5,7 +5,13 @@ import App from './App.vue'
import router from './router' import router from './router'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import '@/assets/styles/element/index.scss'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import AppCard from '@/components/base/AppCard.vue'
import AppList from '@/components/base/AppList.vue'
import AppUpload from '@/components/base/AppUpload.vue'
import AppContainer from '@/components/base/AppContainer.vue'
import modules from './modules' import modules from './modules'
...@@ -13,11 +19,18 @@ import modules from './modules' ...@@ -13,11 +19,18 @@ import modules from './modules'
import './assets/css/base.css' import './assets/css/base.css'
const app = createApp(App) const app = createApp(App)
// 注册公共组件
app
.component('AppCard', AppCard)
.component('AppList', AppList)
.component('AppUpload', AppUpload)
.component('AppContainer', AppContainer)
// 注册模块 // 注册模块
modules({ router }) modules({ router })
app.use(createPinia()) app.use(createPinia())
app.use(router) app.use(router)
app.use(ElementPlus) app.use(ElementPlus, { locale: zhCn, size: 'large' })
app.mount('#app') app.mount('#app')
...@@ -133,6 +133,57 @@ const rows: Record<string, any>[] = [ ...@@ -133,6 +133,57 @@ const rows: Record<string, any>[] = [
] ]
} }
], ],
[
{
title: '招聘与就业',
items: [
{
icon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_1.png',
hoverIcon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_1_hover.png',
title: '企业注册',
href: '/hr/company/create'
},
{
icon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_2.png',
hoverIcon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_2_hover.png',
title: '招聘岗位',
href: '/hr/posts/job'
},
{
className: 'is-blue',
icon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_3.png',
hoverIcon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_3_hover.png',
title: '校友人才',
href: '/hr/alumni'
},
{
className: 'is-blue',
icon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_4.png',
hoverIcon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_4_hover.png',
title: '岗位求职',
href: '/hr/job'
}
]
},
{
attrs: { style: 'flex: 0 0 484px;' },
title: '项目合作',
items: [
{
icon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_5.png',
hoverIcon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_5_hover.png',
title: '发布项目',
href: '/project/create'
},
{
icon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_6.png',
hoverIcon: 'https://webapp-pub.ezijing.com/project/saas/icon_7_6_hover.png',
title: '寻找项目',
href: '/project'
}
]
}
],
[ [
{ {
title: '1+X技能认证', title: '1+X技能认证',
...@@ -199,28 +250,19 @@ const rows: Record<string, any>[] = [ ...@@ -199,28 +250,19 @@ const rows: Record<string, any>[] = [
<template> <template>
<div class="home"> <div class="home">
<div class="home-inner"> <!-- <Total></Total> -->
<!-- <Total></Total> --> <template v-for="(row, index) in rows" :key="index">
<template v-for="(row, index) in rows" :key="index"> <section class="row">
<section class="row"> <div class="row-cell" v-bind="cell.attrs" v-for="(cell, i) in row" :key="i">
<div class="row-cell" v-bind="cell.attrs" v-for="(cell, i) in row" :key="i"> <Card :data="cell"></Card>
<Card :data="cell"></Card> </div>
</div> </section>
</section> </template>
</template>
</div>
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss">
.home { .home {
background: #f9f8f8 url('https://webapp-pub.ezijing.com/project/saas/bg.png') no-repeat center bottom;
background-size: cover;
background-attachment: fixed;
}
.home-inner {
width: 1200px;
margin: 0 auto;
padding-top: 20px; padding-top: 20px;
overflow: hidden; overflow: hidden;
} }
......
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/hr/alumni',
component: AppLayout,
children: [
{
path: '',
component: () => import('./views/List.vue')
},
{
path: 'view/:id',
component: () => import('./views/View.vue'),
props: true
}
]
}
]
<script setup lang="ts">
const menus = [
{ title: '发布职位', path: '/hr/posts/job' },
{ title: '校友人才', path: '/hr/alumni' }
]
const listOptions = {
filters: [
{ type: 'input', label: '公司名称', prop: 'name' },
{ type: 'select', label: '所在行业', prop: 'type' },
{ type: 'select', label: '省份', prop: 'education' }
],
columns: [
{ label: '姓名', prop: 'username' },
{ label: '性别', prop: 'type' },
{ label: '班级', prop: 'class_id' },
{ label: '项目', prop: 'project_prefix' },
{ label: '省份', prop: 'education' },
{ label: '城市', prop: 'education' },
{ label: '所在行业', prop: 'education' },
{ label: '公司名称', prop: 'education' },
{ label: '操作', prop: 'education' }
],
data: [{ username: '哈哈哈' }, { username: '哈哈哈' }]
}
</script>
<template>
<AppContainer :menus="menus" background="white">
<AppList v-bind="listOptions"> </AppList>
</AppContainer>
</template>
<style lang="scss" scoped>
::v-deep(.table-list-filter) {
padding: 30px 30px 10px;
background: #f8f8f8;
border-radius: 20px;
}
</style>
<script setup lang="ts">
const menus = [
{ title: '发布职位', path: '/hr/posts/job' },
{ title: '校友人才', path: '/hr/alumni' }
]
const listOptions = {
remote: {},
filters: [
{ type: 'input', label: '岗位名称', prop: 'name' },
{ type: 'select', label: '岗位类型', prop: 'type' },
{ type: 'select', label: '学历要求', prop: 'education' },
{ type: 'select', label: '工作地点', prop: 'work_locations' }
],
columns: [
{ label: '编号', prop: 'name' },
{ label: '岗位名称', prop: 'name' },
{ label: '岗位类型', prop: 'type' },
{ label: '地点', prop: 'work_locations' },
{ label: '学历要求', prop: 'education' },
{ label: '薪酬范围', prop: 'education' },
{ label: '投递人数', prop: 'education' },
{ label: '状态', prop: 'status' },
{ label: '操作', prop: 'education' }
]
}
</script>
<template>
<AppContainer :menus="menus" background="white">
<AppList v-bind="listOptions">
<router-link to="/hr/posts/job/create">
<el-button type="primary">发布岗位</el-button>
</router-link>
</AppList>
</AppContainer>
</template>
import httpRequest from '@/utils/axios'
import type { CompanyType } from './types'
// 创建公司
export function createCompany(data: CompanyType) {
return httpRequest.post('/api/psp/backend/banner/create', data)
}
// 更新公司
export function updateCompany(data: CompanyType) {
return httpRequest.post('/api/psp/backend/banner/update', data)
}
// 获取公司详情
export function getCompany(params: { id: string }) {
return httpRequest.get('/api/psp/backend/banner/view', { params })
}
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/hr/company',
component: AppLayout,
children: [
{
path: 'create',
component: () => import('./views/Update.vue')
},
{
path: 'view/:id',
component: () => import('./views/View.vue'),
props: true
}
]
}
]
export interface CompanyType {
id?: string
name: string
logo: string
desc: string
email: string
nature: string
code: string
business_licence: string
}
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import type { FormInstance } from 'element-plus'
import AppEditor from '@/components/tinymce/Index.vue'
import { createCompany, updateCompany, getCompany } from '../api'
import type { CompanyType } from '../types'
import { companyTypeList } from '@/utils/dictionary'
const props = defineProps<{ id?: string }>()
const router = useRouter()
const formRef = ref<FormInstance>()
const form = reactive<CompanyType>({
logo: '',
name: '',
desc: '',
email: '',
nature: '',
code: '',
business_licence: ''
})
const rules = {
logo: [{ required: true, message: '请上传公司LOGO', trigger: 'change' }],
name: [{ required: true, message: '请输入公司名称', trigger: 'blur' }],
desc: [{ required: true, message: '请输入公司介绍', trigger: 'blur' }],
email: [{ required: true, message: '请输入企业邮箱', trigger: 'blur' }],
nature: [{ required: true, message: '请选择公司性质', trigger: 'blur' }],
code: [{ required: true, message: '请输入社会统一信用代码', trigger: 'blur' }],
business_licence: [{ required: true, message: '请上传营业执照附件', trigger: 'change' }]
}
// 提交
const onSubmit = () => {
if (!formRef.value) return
formRef.value.validate().then(() => {
props.id ? update() : create()
})
}
// 取消
const onCancel = () => {
router.replace('/banner')
}
// 创建
const create = () => {
createCompany(form).then(() => {
ElMessage({ message: '创建成功', type: 'success' })
router.push('/banner')
})
}
// 修改
const update = () => {
const params = { ...form, id: props.id as string }
updateCompany(params).then(() => {
ElMessage({ message: '修改成功', type: 'success' })
router.push('/banner')
})
}
onMounted(() => {
props.id &&
getCompany({ id: props.id }).then(res => {
Object.assign(form, res.data)
})
})
</script>
<template>
<AppContainer class="company-register">
<h1 class="company-register__title">企业信息注册</h1>
<p class="company-register__tips">请完成企业信息认证,完成后即可发布职位</p>
<AppCard>
<div class="company-register__status">
<p style="color: #60b0ea">待审核</p>
<p style="color: #1dc388">审核通过</p>
<p style="color: #898989">审核不通过</p>
<p style="color: #f8524b">已禁用</p>
</div>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="170px"
label-position="left"
status-icon
style="margin: 0 190px"
>
<el-form-item label="公司LOGO" prop="logo">
<AppUpload v-model="form.logo" accept="image/*"></AppUpload>
</el-form-item>
<el-form-item label="公司名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
</el-form-item>
<el-form-item label="公司介绍" prop="desc">
<AppEditor v-model="form.desc" placeholder="请输入"></AppEditor>
</el-form-item>
<el-form-item label="企业邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入" />
</el-form-item>
<el-form-item label="公司性质" prop="nature">
<el-select v-model="form.nature" placeholder="请选择" style="width: 100%">
<el-option v-for="item in companyTypeList" :key="item.value" v-bind="item"></el-option>
</el-select>
</el-form-item>
<el-form-item label="社会统一信用代码" prop="code">
<el-input v-model="form.code" placeholder="请输入" />
</el-form-item>
<el-form-item label="营业执照附件" prop="business_licence">
<AppUpload v-model="form.business_licence" accept="image/*"></AppUpload>
</el-form-item>
<el-row justify="center" style="margin: 60px 0 20px">
<el-button type="primary" auto-insert-space @click="onSubmit">提交</el-button>
<el-button auto-insert-space @click="onCancel">取消</el-button>
</el-row>
</el-form>
</AppCard>
</AppContainer>
</template>
<style lang="scss" scoped>
.company-register__status {
padding: 10px 60px;
margin-bottom: 50px;
text-align: right;
}
.company-register__title {
font-size: 28px;
font-weight: 500;
line-height: 48px;
color: #333333;
}
.company-register__tips {
margin-bottom: 10px;
font-size: 18px;
font-family: Source Han Sans CN;
font-weight: 400;
line-height: 31px;
color: #666666;
}
</style>
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/hr/job',
component: AppLayout,
children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: 'view/:id', component: () => import('./views/View.vue'), props: true }
]
}
]
<script setup lang="ts">
import AppList from '@/components/base/AppList.vue'
import { jobTypeList, educationList } from '@/utils/dictionary'
const appList = $ref<InstanceType<typeof AppList> | null>(null)
const listOptions = {
remote: {},
filters: [
{ type: 'input', label: '企业名称', prop: 'name', placeholder: '请输入' },
{ type: 'select', label: '岗位类型', prop: 'type', options: jobTypeList },
{ type: 'select', label: '工作地点', prop: 'work_locations' },
{ type: 'select', label: '学历要求', prop: 'education', options: educationList },
{ type: 'input', label: '岗位名称', prop: 'name', placeholder: '请输入' }
],
columns: [
{ label: '编号', prop: 'id' },
{ label: '企业名称', prop: 'name' },
{ label: '岗位类型', prop: 'type' },
{ label: '地点', prop: 'work_locations' },
{ label: '学历要求', prop: 'education' },
{ label: '薪酬范围', prop: 'education' },
{ label: '投递人数', prop: 'education' },
{ label: '状态', prop: 'status' },
{ label: '操作', slots: 'table-actions' }
],
data: [
{ id: '1', name: '企业名称', education: 1, type: 1, salary_min: 10000, salary_max: 20000, desc: '描述' },
{ id: '2', name: '企业名称', education: 1, type: 1, salary_min: 10000, salary_max: 20000, desc: '描述' }
]
}
</script>
<template>
<AppContainer>
<AppList v-bind="listOptions" ref="appList">
<template #table-actions="{ row }">
<router-link :to="`/hr/posts/job/view/${row.id}`" target="_blank">
<el-button link>查看</el-button>
</router-link>
<el-button link>启用</el-button>
<el-button link>禁用</el-button>
<router-link :to="`/hr/posts/job/update/${row.id}`">
<el-button link>编辑</el-button>
</router-link>
</template>
</AppList>
</AppContainer>
</template>
<style lang="scss" scoped>
::v-deep(.table-list-filter) {
padding: 30px 30px 10px;
background: #fff;
border-radius: 20px;
}
</style>
import httpRequest from '@/utils/axios'
import type { JobType } from './types'
// 创建岗位
export function createCompany(data: JobType) {
return httpRequest.post('/api/psp/backend/banner/create', data)
}
// 更新岗位
export function updateCompany(data: JobType) {
return httpRequest.post('/api/psp/backend/banner/update', data)
}
// 获取岗位详情
export function getCompany(params: { id: string }) {
return httpRequest.get('/api/psp/backend/banner/view', { params })
}
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/hr/posts',
component: AppLayout,
children: [
{ path: 'job', component: () => import('./views/Job.vue') },
{ path: 'job/create', component: () => import('./views/JobUpdate.vue') },
{ path: 'job/update/:id', component: () => import('./views/JobUpdate.vue') },
{
path: 'job/view/:id',
component: () => import('./views/JobView.vue'),
props: true
}
]
}
]
export interface JobType {
id?: string
name: string
education: number | undefined
type: number | undefined
work_locations: string
salary_min: number | undefined
salary_max: number | undefined
desc: string
}
<script setup lang="ts">
import AppList from '@/components/base/AppList.vue'
import { jobTypeList, educationList } from '@/utils/dictionary'
const menus = [
{ title: '发布职位', path: '/hr/posts/job' },
{ title: '校友人才', path: '/hr/alumni' }
]
const appList = $ref<InstanceType<typeof AppList> | null>(null)
const listOptions = {
remote: {},
filters: [
{ type: 'input', label: '岗位名称', prop: 'name', placeholder: '请输入' },
{ type: 'select', label: '岗位类型', prop: 'type', options: jobTypeList },
{ type: 'select', label: '学历要求', prop: 'education', options: educationList },
{ type: 'select', label: '工作地点', prop: 'work_locations' }
],
columns: [
{ label: '编号', prop: 'id' },
{ label: '岗位名称', prop: 'name' },
{ label: '岗位类型', prop: 'type' },
{ label: '地点', prop: 'work_locations' },
{ label: '学历要求', prop: 'education' },
{ label: '薪酬范围', prop: 'education' },
{ label: '投递人数', prop: 'education' },
{ label: '状态', prop: 'status' },
{ label: '操作', slots: 'table-actions' }
],
data: [
{ id: '1', name: '岗位名称', education: 1, type: 1, salary_min: 10000, salary_max: 20000, desc: '描述' },
{ id: '2', name: '岗位名称', education: 1, type: 1, salary_min: 10000, salary_max: 20000, desc: '描述' }
]
}
</script>
<template>
<AppContainer :menus="menus" background="white">
<AppList v-bind="listOptions" ref="appList">
<div style="margin: 20px 0">
<router-link to="/hr/posts/job/create">
<el-button type="primary">发布岗位</el-button>
</router-link>
</div>
<template #table-actions="{ row }">
<router-link :to="`/hr/posts/job/view/${row.id}`" target="_blank">
<el-button link>查看</el-button>
</router-link>
<el-button link>启用</el-button>
<el-button link>禁用</el-button>
<router-link :to="`/hr/posts/job/update/${row.id}`">
<el-button link>编辑</el-button>
</router-link>
</template>
</AppList>
</AppContainer>
</template>
<style lang="scss" scoped>
::v-deep(.table-list-filter) {
padding: 30px 30px 10px;
background: #f8f8f8;
border-radius: 20px;
}
</style>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { createCompany, updateCompany, getCompany } from '../api'
import type { JobType } from '../types'
import { jobTypeList, educationList } from '@/utils/dictionary'
const menus = [
{ title: '发布职位', path: '/hr/posts/job' },
{ title: '校友人才', path: '/hr/alumni' }
]
const props = defineProps<{ id?: string }>()
const router = useRouter()
const formRef = ref<FormInstance>()
const form = reactive<JobType>({
name: '',
education: undefined,
type: undefined,
work_locations: '',
salary_min: undefined,
salary_max: undefined,
desc: ''
})
const rules = {
name: [{ required: true, message: '请输入岗位名称', trigger: 'blur' }],
education: [{ required: true, message: '请选择学历要求', trigger: 'change' }],
type: [{ required: true, message: '请选择岗位类型', trigger: 'change' }],
work_locations: [{ required: true, message: '请输入工作地点', trigger: 'blur' }],
salary_min: [{ required: true, message: '请输入薪酬范围', trigger: 'blur' }],
desc: [{ required: true, message: '请输入岗位描述', trigger: 'blur' }]
}
// 提交
const onSubmit = () => {
if (!formRef.value) return
formRef.value.validate().then(() => {
props.id ? update() : create()
})
}
// 取消
const onCancel = () => {
router.replace('/hr/posts/job')
}
// 创建
const create = () => {
createCompany(form).then(() => {
ElMessage({ message: '创建成功', type: 'success' })
router.push('/hr/posts/job')
})
}
// 修改
const update = () => {
const params = { ...form, id: props.id as string }
updateCompany(params).then(() => {
ElMessage({ message: '修改成功', type: 'success' })
router.push('/hr/posts/job')
})
}
onMounted(() => {
props.id &&
getCompany({ id: props.id }).then(res => {
Object.assign(form, res.data)
})
})
</script>
<template>
<AppContainer :menus="menus" background="white">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="90px"
label-position="left"
size="large"
style="margin: 80px 290px 60px"
>
<el-form-item label="岗位名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" maxlength="20" />
</el-form-item>
<el-form-item label="学历要求" prop="education">
<el-select v-model="form.education" placeholder="请选择" style="width: 100%">
<el-option v-for="item in educationList" :key="item.value" v-bind="item"></el-option>
</el-select>
</el-form-item>
<el-form-item label="岗位类型" prop="type">
<el-select v-model="form.type" placeholder="请选择" style="width: 100%">
<el-option v-for="item in jobTypeList" :key="item.value" v-bind="item"></el-option>
</el-select>
</el-form-item>
<el-form-item label="工作地点" prop="work_locations">
<el-input v-model="form.work_locations" placeholder="请输入" />
</el-form-item>
<el-form-item label="薪酬范围" prop="salary_min">
<el-row>
<el-col :span="10">
<el-input-number :controls="false" step-strictly v-model="form.salary_min" style="width: 100%" />
</el-col>
<el-col :span="4" style="text-align: center"></el-col>
<el-col :span="10">
<el-input-number :controls="false" step-strictly v-model="form.salary_max" style="width: 100%" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="岗位描述" prop="desc">
<el-input type="textarea" v-model="form.desc" :autosize="{ minRows: 6, maxRows: 10 }" maxlength="200" />
</el-form-item>
<el-row justify="center" style="margin: 40px 0 20px">
<el-button type="primary" auto-insert-space @click="onSubmit">发布</el-button>
<el-button auto-insert-space @click="onCancel">取消</el-button>
</el-row>
</el-form>
</AppContainer>
</template>
<script setup lang="ts">
const menus = [
{ title: '发布职位', path: '/hr/posts/job' },
{ title: '校友人才', path: '/hr/alumni' }
]
const listOptions = {
remote: {},
columns: [
{ label: '编号', prop: 'name' },
{ label: '姓名', prop: 'name' },
{ label: '已投递简历数', prop: 'type' },
{ label: '所在项目', prop: 'work_locations' },
{ label: '操作', slots: 'table-actions' }
]
}
</script>
<template>
<AppContainer :menus="menus" background="white">
<h2>岗位详情</h2>
<AppList v-bind="listOptions">
<template #table-actions> 查看 </template>
</AppList>
</AppContainer>
</template>
import httpRequest from '@/utils/axios'
import type { CompanyType } from './types'
// 创建公司
export function createCompany(data: CompanyType) {
return httpRequest.post('/api/psp/backend/banner/create', data)
}
// 更新公司
export function updateCompany(data: CompanyType) {
return httpRequest.post('/api/psp/backend/banner/update', data)
}
// 获取公司详情
export function getCompany(params: { id: string }) {
return httpRequest.get('/api/psp/backend/banner/view', { params })
}
<script setup lang="ts">
import type { ProjectType } from '../types'
defineProps<{ data: ProjectType }>()
</script>
<template>
<div class="project-item">
<img :src="data.logo" class="project-item__pic" />
<div class="project-item__content">
<h2>{{ data.name }}</h2>
<p>{{ data.desc }}</p>
</div>
</div>
</template>
<style lang="scss"></style>
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/project',
component: AppLayout,
children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: 'create', component: () => import('./views/Update.vue') },
{ path: 'update/:id', component: () => import('./views/Update.vue') },
{ path: 'view/:id', component: () => import('./views/View.vue'), props: true }
]
}
]
export interface ProjectType {
id: string
name: string
logo: string
type: 1 | 2 | 3
desc: string
contact: string
contact_mobile: string
status: 1 | 2 | 3 | 4
start_time: string
end_time: string
documents: string[]
}
export interface CompanyType {
id?: string
name: string
logo: string
desc: string
email: string
nature: string
code: string
business_licence: string
}
<script setup lang="ts">
import type { ProjectType } from '../types'
import ProjectItem from '../components/ProjectItem.vue'
const projectList = ref<ProjectType[]>([])
</script>
<template>
<AppContainer background="#fff">
<section>
<h2 class="section-title">我的项目</h2>
<ProjectItem v-for="item in projectList" :key="item.id" :data="item"></ProjectItem>
</section>
<section>
<h2 class="section-title">项目列表</h2>
<ProjectItem v-for="item in projectList" :key="item.id" :data="item"></ProjectItem>
</section>
</AppContainer>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import type { FormInstance } from 'element-plus'
import AppEditor from '@/components/tinymce/Index.vue'
import { createCompany, updateCompany, getCompany } from '../api'
import type { CompanyType } from '../types'
const props = defineProps<{ id?: string }>()
const router = useRouter()
const formRef = ref<FormInstance>()
const form = reactive<CompanyType>({
logo: '',
name: '',
desc: '',
email: '',
nature: '',
code: '',
business_licence: ''
})
const rules = {
logo: [{ required: true, message: '请上传项目图片', trigger: 'change' }],
name: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
desc: [{ required: true, message: '请输入项目介绍', trigger: 'blur' }],
email: [{ required: true, message: '请输入企业邮箱', trigger: 'blur' }],
nature: [{ required: true, message: '请选择项目类型', trigger: 'blur' }],
code: [{ required: true, message: '请输入社会统一信用代码', trigger: 'blur' }],
business_licence: [{ required: true, message: '请上传营业执照附件', trigger: 'change' }]
}
// 提交
const onSubmit = () => {
if (!formRef.value) return
formRef.value.validate().then(() => {
props.id ? update() : create()
})
}
// 取消
const onCancel = () => {
router.replace('/project')
}
// 创建
const create = () => {
createCompany(form).then(() => {
ElMessage({ message: '创建成功', type: 'success' })
router.push('/project')
})
}
// 修改
const update = () => {
const params = { ...form, id: props.id as string }
updateCompany(params).then(() => {
ElMessage({ message: '修改成功', type: 'success' })
router.push('/project')
})
}
onMounted(() => {
props.id &&
getCompany({ id: props.id }).then(res => {
Object.assign(form, res.data)
})
})
</script>
<template>
<AppContainer background="#fff">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="90px"
label-position="left"
status-icon
size="large"
style="margin: 0 290px"
>
<el-form-item label="项目图片" prop="logo">
<AppUpload v-model="form.logo" accept="image/*"></AppUpload>
</el-form-item>
<el-form-item label="项目名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" />
</el-form-item>
<el-form-item label="项目类型" prop="nature">
<el-input v-model="form.nature" placeholder="请选择" />
</el-form-item>
<el-form-item label="开始时间" prop="nature">
<el-input v-model="form.nature" placeholder="请选择" />
</el-form-item>
<el-form-item label="结束时间" prop="nature">
<el-input v-model="form.nature" placeholder="请选择" />
</el-form-item>
<el-form-item label="项目介绍" prop="desc">
<AppEditor v-model="form.desc" placeholder="请输入"></AppEditor>
</el-form-item>
<el-form-item label="项目文件" prop="business_licence">
<AppUpload v-model="form.business_licence" accept="image/*"></AppUpload>
</el-form-item>
<el-form-item>
<el-button type="primary" auto-insert-space @click="onSubmit">发布项目</el-button>
<el-button auto-insert-space @click="onCancel">取消</el-button>
</el-form-item>
</el-form>
</AppContainer>
</template>
// json to array
export const json2Array = function (data: Record<string, any>, isValueToNumber = true) {
return Object.keys(data).map(value => ({ label: data[value], value: isValueToNumber ? parseInt(value) : value }))
}
// 公司性质
export const companyType = {
1: '国有企业',
2: '集体企业',
3: '联营企业',
4: '股份合作制企业',
5: '私营企业',
6: '个体户',
7: '合伙企业',
8: '有限责任公司',
9: '股份有限公司营业性质'
}
// 公司性质列表
export const companyTypeList = json2Array(companyType)
// 状态
export const status = {
1: '启用',
2: '禁用',
3: '待审核',
4: '审核不通过'
}
// 状态列表
export const statusList = json2Array(status)
// 学历
export const education = {
1: '高中或以下',
2: '大专',
3: '本科',
4: '硕士研究生',
5: '博士'
}
// 学历列表
export const educationList = json2Array(education)
// 岗位类型
export const jobType = {
1: '管理类',
2: '专业技术类',
3: '专业支持类',
4: '营销类',
5: '操作类'
}
// 岗位类型列表
export const jobTypeList = json2Array(jobType)
// 项目类型
export const projectType = {
1: '合作项目',
2: '创新项目',
3: '招聘项目'
}
// 项目类型列表
export const projectTypeList = json2Array(projectType)
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论