Commit 9d61862a by yuzhenWang

Merge branch 'feature-20251125wyz-做产品上架' into 'dev'

Feature 20251125wyz 做产品上架

See merge request !6
parents 3c82c9ed 710c08f3
......@@ -5,4 +5,4 @@ VITE_APP_TITLE = 银盾中台系统
VITE_APP_ENV = 'development'
# 若依管理系统/开发环境
VITE_APP_BASE_API = 'http://10.0.10.26:9002'
VITE_APP_BASE_API = 'http://139.224.145.34:9002'
# 使用 Node.js 18 基础镜像 (Debian bullseye)
FROM docker.m.daocloud.io/library/node:18-bullseye AS build
# 设置环境变量
ENV npm_config_canvas_binary_host_mirror="https://npmmirror.com/mirrors/canvas"
ENV npm_config_sharp_binary_host="https://npmmirror.com/mirrors/sharp"
ENV npm_config_sharp_libvips_binary_host="https://npmmirror.com/mirrors/sharp-libvips"
# 添加 esbuild 镜像源
ENV ESBUILD_BINARY_HOST="https://npmmirror.com/mirrors/esbuild"
# 安装系统依赖 (使用阿里云镜像源)
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
apt-get update && apt-get install -y --no-install-recommends \
build-essential \
python3 \
pkg-config \
libcairo2-dev \
libpango1.0-dev \
libjpeg-dev \
libgif-dev \
librsvg2-dev \
libvips-dev \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 复制包管理文件
COPY package.json ./
# 1. 显式安装 esbuild 二进制
RUN npm install @esbuild/linux-x64 --verbose --registry=https://registry.npmmirror.com
# 2. 安装其他原生模块
RUN npm install canvas@2.11.2 --verbose --ignore-scripts
RUN npm install sharp@0.32.6 --verbose --ignore-scripts
# 3. 安装项目依赖(移除 --no-optional)
RUN npm install --verbose --registry=https://registry.npmmirror.com
# 4. 验证 esbuild 安装
RUN ls -la node_modules/esbuild/bin && \
./node_modules/.bin/esbuild --version
# 复制源码并构建
COPY . .
RUN npm run build:dev
# 生产阶段
FROM docker.m.daocloud.io/library/nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 设置时区
RUN echo "https://mirrors.aliyun.com/alpine/v3.22/main/" > /etc/apk/repositories && \
echo "https://mirrors.aliyun.com/alpine/v3.22/community/" >> /etc/apk/repositories && \
apk update && \
apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
EXPOSE 6699
CMD ["nginx", "-g", "daemon off;"]
server {
listen 6699;
server_name 139.224.145.34;
# 处理前端静态资源(Vue应用)
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html; # 处理Vue路由history模式
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
limit_except GET POST PUT DELETE OPTIONS {
deny all;
}
}
}
......@@ -7,9 +7,10 @@
"type": "module",
"scripts": {
"dev": "vite",
"build:prod": "vite build",
"build:stage": "vite build --mode staging",
"preview": "vite preview"
"build": "vite build",
"build:dev": "vite build --mode=development",
"preview": "vite preview",
"report": "npm run build --report"
},
"repository": {
"type": "git",
......@@ -28,6 +29,7 @@
"js-beautify": "1.14.11",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"lodash-es": "^4.17.21",
"nprogress": "0.2.0",
"pinia": "3.0.2",
"splitpanes": "^4.0.4",
......
......@@ -20,9 +20,17 @@ export function uploadImage(data) {
})
}
// 查询用户是否有访问项目的权限
export function getVisitPermission(projectBizId) {
export function getVisitPermission(projectBizId, tenantBizId) {
return request({
url: '/user/api/sysUser/login/permission/project/visit?projectBizId=' + projectBizId,
url: `/user/api/sysUser/login/permission/project/visit?projectBizId=${projectBizId}&tenantBizId=${tenantBizId}`,
method: 'get'
})
}
//获取保险公司列表
export function getAllCompanys(data) {
return request({
url: '/user/api/sysDept/company/page',
method: 'post',
data: data
})
}
import request from '@/utils/request'
// 查询产品上架列表
export function getProductList(data) {
return request({
url: '/product/api/product/page',
method: 'post',
data: data
})
}
// 查询产品上架列表
export function getCategoryTreeList(data) {
return request({
url: '/base/api/category/page',
method: 'post',
data: data
})
}
// 新增产品
export function addProduct(data) {
return request({
url: '/product/api/productLaunch/add/category',
method: 'post',
data: data
})
}
// 获取产品详情
export function getProductDetail(productLaunchBizId) {
return request({
url: `/product/api/productLaunch/detail?productLaunchBizId=${productLaunchBizId}`,
method: 'get'
})
}
// 查询产品参数信息
export function productParams(data) {
return request({
url: '/base/api/relObjectField/query',
method: 'post',
data: data
})
}
// 查询产品参数右侧下拉框数据
export function ParamRightOptions(data) {
return request({
url: '/base/api/relFieldValue/field/list',
method: 'post',
data: data
})
}
// 查询产品规格信息列表
export function productSpecies(data) {
return request({
url: '/base/api/relObjectSpecies/query',
method: 'post',
data: data
})
}
// 查询选中的分类列表
export function querySelectCategory(data) {
return request({
url: '/base/api/relObjectCategory/query/selected',
method: 'post',
data: data
})
}
// 保存上架产品
export function saveProductLaunch(data) {
return request({
url: '/product/api/productLaunch/save',
method: 'post',
data: data
})
}
// 发佣管理更新规格信息
export function exportSpecies(data) {
return request({
url: '/product/api/announcementSpecies/import/species',
method: 'post',
data: data
})
}
// 发佣管理查询公告佣列表
export function sendSpecies(data) {
return request({
url: '/product/api/announcementSpecies/page',
method: 'post',
data: data
})
}
// 发佣管理查询公告佣金明细列表
export function sendCommissionRatio(data) {
return request({
url: '/product/api/announcementCommissionRatio/page',
method: 'post',
data: data
})
}
// 发佣管理批量保存公告佣金设置
export function saveBatchSendCommission(data) {
return request({
url: '/product/api/announcementCommissionRatio/batch/save',
method: 'post',
data: data
})
}
// 发佣管理单个删除公告佣金设置
export function deleteCommission(announcementCommissionRatioBizId) {
return request({
url: `/product/api/announcementCommissionRatio/del?announcementCommissionRatioBizId=${announcementCommissionRatioBizId}`,
method: 'delete'
})
}
// 获得来佣列表的数据
export function comeCommissionList(data) {
return request({
url: '/product/api/expectedSpecies/page',
method: 'post',
data: data
})
}
// 获得来佣列表的数据
export function comeExpectedSpecies(data) {
return request({
url: '/product/api/expectedSpecies/import/species',
method: 'post',
data: data
})
}
// 批量保存来佣佣金设置
export function comeBatchSave(data) {
return request({
url: '/product/api/expectedCommissionRatio/batch/save',
method: 'post',
data: data
})
}
// 批量保存来佣佣金设置
export function comeCommissionRatio(data) {
return request({
url: '/product/api/expectedCommissionRatio/page',
method: 'post',
data: data
})
}
// 来佣管理单个删除佣金设置
export function deleteComeCommission(expectedCommissionRatioBizId) {
return request({
url: `/product/api/expectedCommissionRatio/del?expectedCommissionRatioBizId=${expectedCommissionRatioBizId}`,
method: 'delete'
})
}
// 商品列表
export function goodsList(data) {
return request({
url: '/product/api/productLaunch/page',
method: 'post',
data: data
})
}
// 审核商品状态
export function productApproval(data) {
return request({
url: '/product/api/productLaunch/approval',
method: 'put',
data: data
})
}
// 修改商品上架下架状态
export function changeProductStatus(data) {
return request({
url: '/product/api/productLaunch/edit/status',
method: 'put',
data: data
})
}
// 修改来佣佣金设置状态
export function changeComeStatus(data) {
return request({
url: `/product/api/expectedCommissionRatio/edit/status?expectedCommissionRatioBizId=${data.expectedCommissionRatioBizId}&status=${data.status}`,
method: 'put'
})
}
// 修改发佣佣金设置状态
export function changeSendStatus(data) {
return request({
url: `/product/api/announcementCommissionRatio/edit/status?announcementCommissionRatioBizId=${data.announcementCommissionRatioBizId}&status=${data.status}`,
method: 'put'
})
}
<template>
<div class="box">
<el-row>
<el-col :span="12">
<!-- v-model="queryParams.productName"
@blur="handleSelectChange"
:remote-method="searchProduct"
-->
<!-- <el-select
filterable
remote
default-first-option
:reserve-keyword="false"
placeholder="类目搜索"
clearable
remote-show-suffix
style="margin-bottom: 10px"
>
<el-option
v-for="item in productList"
:key="item.productBizId"
:label="item.productName"
:value="item.productBizId"
/>
</el-select> -->
</el-col>
<el-col :span="24" v-if="categoryName">
<div class="chooseName">
<span style="color: rgb(0 0 0 / 55%)">已选类目:</span>{{ categoryName }}
</div>
</el-col>
</el-row>
<div class="category-container">
<div class="tableHeader">
<div
class="headerItem"
:style="{ width: `${100 / categoryList.length}%` }"
v-for="item in categoryList"
:key="item.level"
>
{{ item.title }}
</div>
</div>
<div class="tableBody">
<div
class="bodyItem"
v-for="(item, index) in categoryList"
:key="item.level"
:style="{
width: `${100 / categoryList.length}%`,
borderRight: categoryList.length - 1 === index ? 'none' : '1px solid #ccc'
}"
>
<div
class="categoryItem"
v-for="(cItem, cIndex) in item.data"
:key="cItem.id"
@click="handleClickCategory(item, cItem, cIndex)"
:style="{
backgroundColor: cItem.isSelected ? 'rgb(64, 158, 255,0.1)' : null
}"
>
<span> {{ cItem.name }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { getCategoryTreeList } from '@/api/product/index'
import { computed } from 'vue'
import { ref } from 'vue'
const props = defineProps({
// 类目数据,
data: {
type: Array,
default: () => []
},
// 类型,是新增还是编辑,
type: {
type: String,
default: 'add'
}
})
const categoryList = ref([])
const level1Obj = ref([])
// 中文数字单位
const units = ['', '十', '百', '千']
const bigUnits = ['', '万', '亿', '兆', '京', '垓', '秭', '穰', '沟', '涧', '正', '载']
// 中文数字字符
const digits = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
const categoryName = computed(() => {
let result = []
categoryList.value.forEach(item => {
item.data.forEach(c => {
if (c.isSelected) result.push(c.name)
})
})
if (result.length > 0) {
return result.join(' > ')
} else {
return ''
}
})
const emit = defineEmits(['handleClickCategory'])
const handleClickCategory = (fItem, cItem, cIndex) => {
categoryList.value.forEach(item => {
item.data.forEach(c => {
if (c.categoryBizId == cItem.categoryBizId && fItem.level == item.level) {
c.isSelected = true
} else if (fItem.level == item.level) {
c.isSelected = false
}
})
})
getCategoryData(cItem)
}
function getCategoryData(obj) {
// let name = query ? query.trim() : ''
let data = {
// name: name,
type: 'PRODUCT',
pid: obj.categoryBizId ? obj.categoryBizId : '0',
pageSize: 9999,
pageNo: 1
}
getCategoryTreeList(data).then(response => {
if (response.code === 200) {
let res = response.data.records
res.forEach(item => {
item.isSelected = false
})
if (!obj.level) {
categoryList.value = [
{ level: 1, data: res, title: `${convertSection(1)}级类目` },
{ level: 2, data: [], title: `${convertSection(2)}级类目` },
{ level: 3, data: [], title: `${convertSection(3)}级类目` },
{ level: 4, data: [], title: `${convertSection(4)}级类目` }
]
} else {
if (res.length > 0) {
// 先删除当前层级以后的数据
if (categoryList.value.length > 4 && obj.level > 4) {
categoryList.value.splice(obj.level, categoryList.value.length - obj.level)
}
let cIndex = categoryList.value.findIndex(item => item.level == res[0].level)
if (cIndex > -1) {
// 固定显示4个类目,如果超过4个类目,则删除多余的类目
if (categoryList.value.length > 4 && obj.level < 4) {
categoryList.value.splice(4, categoryList.value.length - 4)
}
categoryList.value[cIndex].data = res
for (let i = cIndex + 1; i < categoryList.value.length; i++) {
categoryList.value[i].data = []
}
} else {
categoryList.value.push({
level: res[0].level,
data: res,
title: `${convertSection(res[0].level)}级类目`
})
}
}
}
console.log('categoryList', categoryList.value)
} else {
categoryList.value = []
proxy.$modal.msgError(response.msg)
}
})
}
// 转换4位数字节
const convertSection = sectionNum => {
const len = sectionNum
let result = ''
let hasZero = false
for (let i = 0; i < len; i++) {
const digit = sectionNum
if (digit === 0) {
hasZero = true
} else {
if (hasZero) {
result += '零'
hasZero = false
}
result += digits[digit]
return result
}
}
// 处理全零的情况
if (result === '') {
return '零'
}
return result
}
if (props.type == 'add') {
getCategoryData({ categoryBizId: '0' })
} else if (props.type == 'edit') {
console.log('props.data', props.data);
// 先回显已经选择好的类目
categoryList.value = JSON.parse(JSON.stringify(props.data))
categoryList.value.forEach(item => {
item.title = `${convertSection(item.level)}级类目`
// 还应该把data里的数据都加上 isSelected: true
})
}
// 暴露给父组件
defineExpose({
categoryList,
categoryName
})
</script>
<style lang="scss" scoped>
.box {
width: 100%;
box-sizing: border-box;
}
.category-container {
width: 100%;
border: 1px solid #ccc;
border-radius: 10px;
overflow: hidden;
height: 100%;
}
.tableHeader {
display: flex;
justify-content: space-between;
border-bottom: 1px solid #ccc;
color: rgb(0 0 0 / 55%);
font-size: 16px;
}
.headerItem {
padding: 10px 20px;
text-align: left;
}
.tableBody {
display: flex;
justify-content: space-between;
color: rgb(0 0 0 / 55%);
font-size: 14px;
height: calc(100% - 40px);
}
.bodyItem {
overflow-y: auto;
padding: 10px 10px;
}
.categoryItem {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 10px;
padding: 10px;
border-radius: 5px;
}
.chooseName {
padding: 10px 20px;
background-color: #f7f7f7;
border-radius: 5px;
font-size: 14px;
margin-bottom: 10px;
}
</style>
......@@ -2,9 +2,11 @@
<section class="app-main">
<router-view v-slot="{ Component, route }">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="tagsViewStore.cachedViews">
<component v-if="!route.meta.link" :is="Component" :key="route.path"/>
</keep-alive>
<!-- 慎用keep-alive缓存标签 -->
<!-- <keep-alive :include="tagsViewStore.cachedViews">
</keep-alive> -->
<component v-if="!route.meta.link" :is="Component" :key="route.path" />
</transition>
</router-view>
<iframe-toggle />
......@@ -13,8 +15,8 @@
</template>
<script setup>
import copyright from "./Copyright/index"
import iframeToggle from "./IframeToggle/index"
import copyright from './Copyright/index'
import iframeToggle from './IframeToggle/index'
import useTagsViewStore from '@/store/modules/tagsView'
const route = useRoute()
......@@ -86,4 +88,3 @@ function addIframe() {
border-radius: 3px;
}
</style>
This diff is collapsed. Click to expand it.
speciesJson
:
"[{\"isIllustration\":\"1\",\"speciesTypeBizId\":\"species_type_IUVxfmyu8RUTHsfq\",\"typeCode\":\"PROTECTION_PLAN\",\"typeName\":\"保障计划\",\"value\":\"70岁\"},{\"illustrationUrl\":\"\",\"isIllustration\":\"0\",\"speciesTypeBizId\":\"species_type_TcQNeGLE4rcBcjsj\",\"typeCode\":\"PAYMENT_TERM\",\"typeName\":\"供款年期\",\"value\":\"3\"},{\"illustrationUrl\":\"\",\"isIllustration\":\"0\",\"speciesTypeBizId\":\"species_type_uvdLgZQHUZQMfv1k\",\"typeCode\":\"SPECIES_TYPE_CODE1616036003\",\"typeName\":\"尺码\",\"value\":\"S\"}]"
status
:
1
......@@ -12,11 +12,10 @@ let downloadLoadingInstance
export let isRelogin = { show: false }
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
baseURL: import.meta.env.VITE_APP_BASE_API || '/api', // 使用环境变量
timeout: 10000
})
......@@ -109,7 +108,7 @@ service.interceptors.response.use(
useUserStore()
.logOut()
.then(() => {
location.href = '/index'
location.href = '/login?redirect=/workbench'
})
})
.catch(() => {
......@@ -123,6 +122,9 @@ service.interceptors.response.use(
} else if (code === 601) {
ElMessage({ message: msg, type: 'warning' })
return Promise.reject(new Error(msg))
} else if (code == 1004) {
// 后端自定义的错误码便于前端显示错误信息,非系统性
return Promise.resolve(res.data)
} else if (code !== 200) {
ElNotification.error({ title: msg })
return Promise.reject('error')
......@@ -133,13 +135,37 @@ service.interceptors.response.use(
error => {
console.log('err' + error)
let { message } = error
if (message == 'Network Error') {
message = '后端接口连接异常'
} else if (message.includes('timeout')) {
message = '系统接口请求超时'
} else if (message == 'Request failed with status code 401') {
if (!isRelogin.show) {
isRelogin.show = true
ElMessageBox.confirm('登录状态已过期,请重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning',
showCancelButton: false
})
.then(() => {
isRelogin.show = false
useUserStore()
.logOut()
.then(() => {
location.href = '/login?redirect=/workbench'
})
})
.catch(() => {
isRelogin.show = false
})
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (message.includes('Request failed with status code')) {
message = '系统接口' + message.substr(message.length - 3) + '异常'
}
ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
}
......
<template>
<div class="app-container">
<el-tabs v-model="activeName" class="demo-tabs" :before-leave="beforeTabLeave">
<el-tab-pane v-for="tab in tabsList" :key="tab.name" :label="tab.label" :name="tab.name">
<ProductBaseInfo
:activeName="activeName"
v-if="tab.name === 'baseInfo'"
:detailInfo="productBaseInfo"
@handleSuccess="handleSuccess"
/>
<div v-if="tab.name === 'comeCommission'">
<ComeInfo :activeName="activeName"></ComeInfo>
</div>
<div v-if="tab.name === 'sendCommission'">
<SendInfo :activeName="activeName"></SendInfo>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup name="InsuranceProduct">
import { getProductDetail } from '@/api/product/index'
import { ref } from 'vue'
import ProductBaseInfo from './components/productBaseInfo.vue'
import SendInfo from './components/sendInfo.vue'
import ComeInfo from './components/comeInfo.vue'
const route = useRoute()
const { proxy } = getCurrentInstance()
const activeName = ref('baseInfo')
const productBaseInfo = ref({}) //产品基本信息
const tabsList = ref([
{
label: '基础信息',
name: 'baseInfo',
key: 'baseInfo',
id: 1
},
{
label: '来佣管理',
name: 'comeCommission',
id: 2,
key: 'comeCommission'
},
{
label: '发佣管理',
name: 'sendCommission',
id: 3,
key: 'sendCommission'
}
])
const handleSuccess = (source, id) => {
if (source === 'baseInfo') {
getProductInfo('comeCommission', id)
}
}
// Tab切换前的验证
const beforeTabLeave = (activeTabName, oldTabName) => {
if (activeTabName !== 'baseInfo' && productBaseInfo.value.apiSpeciesPriceDtoList.length == 0) {
proxy.$modal.msgError('请先提交产品相关信息,再进行下一步操作!')
return false
}
return true
}
/** 获取产品详情 */
function getProductInfo(tabName, id) {
let productLaunchBizId = ''
if (route.query.source && route.query.source == 'copy') {
productLaunchBizId = id
} else {
productLaunchBizId = route.query.productLaunchBizId
}
getProductDetail(productLaunchBizId).then(response => {
if (response.code === 200) {
productBaseInfo.value = response.data
activeName.value = tabName
} else {
proxy.$modal.msgError(response.msg)
}
})
}
if (route.query.source && route.query.source == 'copy') {
getProductInfo('baseInfo', route.query.productLaunchBizId)
} else {
getProductInfo('baseInfo')
}
</script>
<style lang="scss" scoped>
.app-container {
width: 100%;
height: 89.8vh !important;
box-sizing: border-box;
/* background-color: rgb(247 247 247) !important; */
/* padding: 0 !important; */
/* height: 500px !important;
overflow: hidden;
overflow-y: scroll; */
}
</style>
......@@ -76,8 +76,8 @@
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
v-if="(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) || userStore.isSuperAdmin === 1">修改</el-button>
<!-- <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:menu:add']">新增</el-button>-->
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
v-if="(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) || userStore.isSuperAdmin === 1">删除</el-button>
<!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
v-if="(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) || userStore.isSuperAdmin === 1">删除</el-button> -->
</template>
</el-table-column>
</el-table>
......
......@@ -60,6 +60,11 @@
</template>
</el-table-column>
<el-table-column label="项目名称" prop="projectName" />
<el-table-column prop="projectType" label="项目类型">
<template #default="scope">
<dict-tag :options="sys_project_type" :value="scope.row.projectType" />
</template>
</el-table-column>
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
......@@ -129,7 +134,7 @@
"
>修改</el-button
>
<el-button
<!-- <el-button
link
type="primary"
icon="Delete"
......@@ -139,7 +144,7 @@
userStore.isSuperAdmin === 1
"
>删除</el-button
>
> -->
</template>
</el-table-column>
</el-table>
......@@ -158,6 +163,18 @@
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入项目名称" />
</el-form-item>
<!-- 项目类型选择 -->
<el-form-item label="项目类型">
<el-radio-group v-model="form.projectType">
<el-radio
v-for="dict in sys_project_type"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="项目开始时间" prop="startTime">
<el-date-picker
v-model="form.startTime"
......@@ -261,7 +278,7 @@
</el-radio-group>
</el-form-item>
<el-form-item
label="项目图标"
label="项目图标"0
prop="logoUrl"
:rules="[{ required: true, message: '请上传项目图标', trigger: 'change' }]"
>
......@@ -363,7 +380,7 @@ import ImageUpload from '@/components/ImageUpload/index.vue' //图片上传组
const userStore = useUserStore()
const router = useRouter()
const { proxy } = getCurrentInstance()
const { sys_status, sys_scope, sys_no_yes } = proxy.useDict('sys_status', 'sys_scope', 'sys_no_yes')
const { sys_status, sys_scope, sys_no_yes,sys_project_type } = proxy.useDict('sys_status', 'sys_scope', 'sys_no_yes','sys_project_type')
const projectList = ref([])
const open = ref(false)
......
<template>
<div class="app-container">
<el-button type="primary" icon="Back" @click="handleBack" style="margin-bottom: 10px"
>返回</el-button
>
<!-- 选项卡组件:增加 custom-tabs 类名 -->
<el-tabs v-model="activeTab" type="card" @tab-change="handleTabChange" class="custom-tabs">
<!-- 用户权限选项卡 -->
......@@ -620,7 +623,9 @@ import {
import { ref } from 'vue'
import useUserStore from '@/store/modules/user'
import { ElMessage } from 'element-plus'
const router = useRouter()
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const {
......@@ -848,7 +853,7 @@ function importProjectUserListSubmitForm() {
proxy.$modal
.confirm('是否确认导入用户账号为"' + importProjectUserListNameList + '"的数据项?')
.then(function () {
return addImportProjectUserList(importProjectUserListIdList, projectBizId,tenantBizId)
return addImportProjectUserList(importProjectUserListIdList, projectBizId, tenantBizId)
})
.then(() => {
importProjectUserListOpen.value = false
......@@ -943,7 +948,7 @@ function importProjectRoleListSubmitForm() {
proxy.$modal
.confirm('是否确认导入角色名称为"' + importProjectRoleListNameList + '"的数据项?')
.then(function () {
return addImportProjectRoleList(importProjectRoleListIdList, projectBizId,tenantBizId)
return addImportProjectRoleList(importProjectRoleListIdList, projectBizId, tenantBizId)
})
.then(() => {
importProjectRoleListOpen.value = false
......@@ -998,7 +1003,7 @@ function handleImportProjectMenuList() {
// 加载菜单树
const loadMenuTree = async () => {
try {
const res = await getMenuTree(route.query.projectBizId,route.query.tenantBizId)
const res = await getMenuTree(route.query.projectBizId, route.query.tenantBizId)
menuTree.value = res.data // 直接使用后端返回的树形结构
loadImportSelectedMenuList() // 加载选中的菜单列表,更新树勾选
} catch (error) {
......@@ -1009,7 +1014,7 @@ const loadMenuTree = async () => {
// 修改加载选中菜单列表的逻辑
const loadImportSelectedMenuList = async () => {
try {
const res = await getImportSelectedMenuList(route.query.projectBizId,route.query.tenantBizId)
const res = await getImportSelectedMenuList(route.query.projectBizId, route.query.tenantBizId)
const targetKeys = res.data || []
// 开启严格模式(禁用联动)
......@@ -1368,7 +1373,9 @@ const handleTabChange = tabName => {
getMenuList()
}
}
const handleBack = () => {
router.go(-1)
}
getUserList()
</script>
......
......@@ -90,8 +90,8 @@
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
v-if="(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) || userStore.isSuperAdmin === 1">修改</el-button>
<el-button link type="primary" icon="Delete"
v-if="(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) || userStore.isSuperAdmin === 1">删除</el-button>
<!-- <el-button link type="primary" icon="Delete"
v-if="(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) || userStore.isSuperAdmin === 1">删除</el-button> -->
</template>
</el-table-column>
</el-table>
......
......@@ -267,7 +267,6 @@ import { Splitpanes, Pane } from 'splitpanes'
import 'splitpanes/dist/splitpanes.css'
import { ref } from 'vue'
const props = defineProps({
tenantBizId: {
type: String,
......@@ -534,7 +533,7 @@ const getDeptTree = () => {
deptBizIdValue.value = response.data[0].deptBizId
deptOptions.value = response.data
enabledDeptOptions.value = filterDisabledDept(JSON.parse(JSON.stringify(response.data)))
getList()
// getList()
// 等待 DOM 更新后选中第一个节点
nextTick(() => {
selectFirstTreeNode()
......@@ -638,7 +637,10 @@ watch(
newTab => {
if (newTab == 'dept') {
getDeptTree()
resetQuery()
dateRange.value = []
proxy.resetForm('queryRef')
queryParams.value.deptId = undefined
proxy.$refs.deptTreeRef.setCurrentKey(null)
}
},
{ immediate: true }
......
......@@ -63,7 +63,7 @@
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
<el-button v-if="userStore.isSuperAdmin === 1" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
......@@ -124,9 +124,9 @@
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
>修改</el-button
>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
<!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
>删除</el-button
>
> -->
</template>
</el-table-column>
</el-table>
......
<template>
<div class="app-container">
<el-button type="primary" icon="Back" @click="handleBack" style="margin-bottom: 10px"
>返回</el-button
>
<!-- 选项卡组件:增加 custom-tabs 类名 -->
<el-tabs v-model="activeTab" type="card" @tab-change="handleTabChange" class="custom-tabs">
<!-- 项目权限选项卡 -->
......@@ -55,9 +58,9 @@
"
>分配权限</el-button
>
<!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"-->
<!-- >删除</el-button-->
<!-- >-->
<!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"-->
<!-- >删除</el-button-->
<!-- >-->
</template>
</el-table-column>
</el-table>
......@@ -938,7 +941,12 @@ function handleDelete(row) {
/** 项目分配权限 */
function handlePermission(row) {
router.push(`/system/project/permission?tenantBizId=`+route.query.tenantBizId+`&projectBizId=` + row.projectBizId)
router.push(
`/system/project/permission?tenantBizId=` +
route.query.tenantBizId +
`&projectBizId=` +
row.projectBizId
)
}
//========项目-列表结束=========
......@@ -1619,7 +1627,9 @@ const handleTabChange = tabName => {
getMenuList()
}
}
const handleBack = () => {
router.go(-1)
}
getList()
</script>
......
<template>
<div class="app-container">
<h4 class="form-header h4">基本信息</h4>
<el-form :model="form" label-width="80px">
<el-row>
<el-col :span="8" :offset="2">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" disabled />
</el-form-item>
</el-col>
<el-col :span="8" :offset="2">
<el-form-item label="登录账号" prop="userName">
<el-input v-model="form.userName" disabled />
</el-form-item>
</el-col>
</el-row>
</el-form>
<h4 class="form-header h4">角色信息</h4>
<el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="roleRef" @selection-change="handleSelectionChange" :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)">
<el-table-column label="序号" width="55" type="index" align="center">
<template #default="scope">
<span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column type="selection" :reserve-selection="true" :selectable="checkSelectable" width="55"></el-table-column>
<el-table-column label="角色编号" align="center" prop="roleId" />
<el-table-column label="角色名称" align="center" prop="roleName" />
<el-table-column label="权限字符" align="center" prop="roleKey" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="pageNum" v-model:limit="pageSize" />
<el-form label-width="100px">
<div style="text-align: center;margin-left:-120px;margin-top:30px;">
<el-button type="primary" @click="submitForm()">提交</el-button>
<el-button @click="close()">返回</el-button>
</div>
</el-form>
</div>
</template>
<script setup name="AuthRole">
import { getAuthRole, updateAuthRole } from "@/api/system/user"
const route = useRoute()
const { proxy } = getCurrentInstance()
const loading = ref(true)
const total = ref(0)
const pageNum = ref(1)
const pageSize = ref(10)
const roleIds = ref([])
const roles = ref([])
const form = ref({
nickName: undefined,
userName: undefined,
userId: undefined
})
/** 单击选中行数据 */
function clickRow(row) {
if (checkSelectable(row)) {
proxy.$refs["roleRef"].toggleRowSelection(row)
}
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
roleIds.value = selection.map(item => item.roleId)
}
/** 保存选中的数据编号 */
function getRowKey(row) {
return row.roleId
}
// 检查角色状态
function checkSelectable(row) {
return row.status === "0" ? true : false
}
/** 关闭按钮 */
function close() {
const obj = { path: "/system/user" }
proxy.$tab.closeOpenPage(obj)
}
/** 提交按钮 */
function submitForm() {
const userId = form.value.userId
const rIds = roleIds.value.join(",")
updateAuthRole({ userId: userId, roleIds: rIds }).then(response => {
proxy.$modal.msgSuccess("授权成功")
close()
})
}
(() => {
const userId = route.params && route.params.userId
if (userId) {
loading.value = true
getAuthRole(userId).then(response => {
form.value = response.user
roles.value = response.roles
total.value = roles.value.length
nextTick(() => {
roles.value.forEach(row => {
if (row.flag) {
proxy.$refs["roleRef"].toggleRowSelection(row)
}
})
})
loading.value = false
})
}
})()
</script>
<template>
<div class="app-container">
<el-row :gutter="20">
<el-col :span="6" :xs="24">
<el-card class="box-card">
<template v-slot:header>
<div class="clearfix">
<span>个人信息</span>
</div>
</template>
<div>
<div class="text-center">
<userAvatar />
</div>
<ul class="list-group list-group-striped">
<li class="list-group-item">
<svg-icon icon-class="user" />用户名称
<div class="pull-right">{{ state.user.userName }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="phone" />手机号码
<div class="pull-right">{{ state.user.phonenumber }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="email" />用户邮箱
<div class="pull-right">{{ state.user.email }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="tree" />所属部门
<div class="pull-right" v-if="state.user.dept">{{ state.user.dept.deptName }} / {{ state.postGroup }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="peoples" />所属角色
<div class="pull-right">{{ state.roleGroup }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="date" />创建日期
<div class="pull-right">{{ state.user.createTime }}</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :span="18" :xs="24">
<el-card>
<template v-slot:header>
<div class="clearfix">
<span>基本资料</span>
</div>
</template>
<el-tabs v-model="selectedTab">
<el-tab-pane label="基本资料" name="userinfo">
<userInfo :user="state.user" />
</el-tab-pane>
<el-tab-pane label="修改密码" name="resetPwd">
<resetPwd />
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="Profile">
import userAvatar from "./userAvatar"
import userInfo from "./userInfo"
import resetPwd from "./resetPwd"
import { getUserProfile } from "@/api/system/user"
const route = useRoute()
const selectedTab = ref("userinfo")
const state = reactive({
user: {},
roleGroup: {},
postGroup: {}
})
function getUser() {
getUserProfile().then(response => {
state.user = response.data
state.roleGroup = response.roleGroup
state.postGroup = response.postGroup
})
}
onMounted(() => {
const activeTab = route.params && route.params.activeTab
if (activeTab) {
selectedTab.value = activeTab
}
getUser()
})
</script>
<template>
<el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
<el-form-item label="旧密码" prop="oldPassword">
<el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { updateUserPwd } from "@/api/system/user"
const { proxy } = getCurrentInstance()
const user = reactive({
oldPassword: undefined,
newPassword: undefined,
confirmPassword: undefined
})
const equalToPassword = (rule, value, callback) => {
if (user.newPassword !== value) {
callback(new Error("两次输入的密码不一致"))
} else {
callback()
}
}
const rules = ref({
oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }],
newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }],
confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }]
})
/** 提交按钮 */
function submit() {
proxy.$refs.pwdRef.validate(valid => {
if (valid) {
updateUserPwd(user.oldPassword, user.newPassword).then(response => {
proxy.$modal.msgSuccess("修改成功")
})
}
})
}
/** 关闭按钮 */
function close() {
proxy.$tab.closePage()
}
</script>
<template>
<div class="user-info-head" @click="editCropper()">
<img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
<el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
<el-row>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<vue-cropper
ref="cropper"
:img="options.img"
:info="true"
:autoCrop="options.autoCrop"
:autoCropWidth="options.autoCropWidth"
:autoCropHeight="options.autoCropHeight"
:fixedBox="options.fixedBox"
:outputType="options.outputType"
@realTime="realTime"
v-if="visible"
/>
</el-col>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<div class="avatar-upload-preview">
<img :src="options.previews.url" :style="options.previews.img" />
</div>
</el-col>
</el-row>
<br />
<el-row>
<el-col :lg="2" :md="2">
<el-upload
action="#"
:http-request="requestUpload"
:show-file-list="false"
:before-upload="beforeUpload"
>
<el-button>
选择
<el-icon class="el-icon--right"><Upload /></el-icon>
</el-button>
</el-upload>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button icon="Plus" @click="changeScale(1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="Minus" @click="changeScale(-1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshRight" @click="rotateRight()"></el-button>
</el-col>
<el-col :lg="{ span: 2, offset: 6 }" :md="2">
<el-button type="primary" @click="uploadImg()">提 交</el-button>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script setup>
import "vue-cropper/dist/index.css"
import { VueCropper } from "vue-cropper"
import { uploadAvatar } from "@/api/system/user"
import useUserStore from "@/store/modules/user"
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const open = ref(false)
const visible = ref(false)
const title = ref("修改头像")
//图片裁剪数据
const options = reactive({
img: userStore.avatar, // 裁剪图片的地址
autoCrop: true, // 是否默认生成截图框
autoCropWidth: 200, // 默认生成截图框宽度
autoCropHeight: 200, // 默认生成截图框高度
fixedBox: true, // 固定截图框大小 不允许改变
outputType: "png", // 默认生成截图为PNG格式
filename: 'avatar', // 文件名称
previews: {} //预览数据
})
/** 编辑头像 */
function editCropper() {
open.value = true
}
/** 打开弹出层结束时的回调 */
function modalOpened() {
visible.value = true
}
/** 覆盖默认上传行为 */
function requestUpload() {}
/** 向左旋转 */
function rotateLeft() {
proxy.$refs.cropper.rotateLeft()
}
/** 向右旋转 */
function rotateRight() {
proxy.$refs.cropper.rotateRight()
}
/** 图片缩放 */
function changeScale(num) {
num = num || 1
proxy.$refs.cropper.changeScale(num)
}
/** 上传预处理 */
function beforeUpload(file) {
if (file.type.indexOf("image/") == -1) {
proxy.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。")
} else {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => {
options.img = reader.result
options.filename = file.name
}
}
}
/** 上传图片 */
function uploadImg() {
proxy.$refs.cropper.getCropBlob(data => {
let formData = new FormData()
formData.append("avatarfile", data, options.filename)
uploadAvatar(formData).then(response => {
open.value = false
options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl
userStore.avatar = options.img
proxy.$modal.msgSuccess("修改成功")
visible.value = false
})
})
}
/** 实时预览 */
function realTime(data) {
options.previews = data
}
/** 关闭窗口 */
function closeDialog() {
options.img = userStore.avatar
options.visible = false
}
</script>
<style lang='scss' scoped>
.user-info-head {
position: relative;
display: inline-block;
height: 120px;
}
.user-info-head:hover:after {
content: "+";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
color: #eee;
background: rgba(0, 0, 0, 0.5);
font-size: 24px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
line-height: 110px;
border-radius: 50%;
}
</style>
\ No newline at end of file
<template>
<el-form ref="userRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" maxlength="30" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="form.phonenumber" maxlength="11" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" maxlength="50" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.sex">
<el-radio value="0"></el-radio>
<el-radio value="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { updateUserProfile } from "@/api/system/user"
const props = defineProps({
user: {
type: Object
}
})
const { proxy } = getCurrentInstance()
const form = ref({})
const rules = ref({
nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
email: [{ required: true, message: "邮箱地址不能为空", trigger: "blur" }, { type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
phonenumber: [{ required: true, message: "手机号码不能为空", trigger: "blur" }, { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }],
})
/** 提交按钮 */
function submit() {
proxy.$refs.userRef.validate(valid => {
if (valid) {
updateUserProfile(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
props.user.phonenumber = form.value.phonenumber
props.user.email = form.value.email
})
}
})
}
/** 关闭按钮 */
function close() {
proxy.$tab.closePage()
}
// 回显当前登录用户信息
watch(() => props.user, user => {
if (user) {
form.value = { nickName: user.nickName, phonenumber: user.phonenumber, email: user.email, sex: user.sex }
}
},{ immediate: true })
</script>
......@@ -325,6 +325,7 @@ function handleStatusChange(row, event) {
/** 重置新增的表单以及其他数据 */
function reset() {
form.value = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
userBizId: undefined,
userName: undefined,
password: undefined,
......
......@@ -13,7 +13,9 @@ import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { getVisitPermission } from '@/api/common'
import { getToken } from '@/utils/auth'
import useUserStore from '@/store/modules/user'
const userStore = useUserStore()
const props = defineProps({
project: {
type: Object,
......@@ -26,7 +28,10 @@ const appName = computed(() => props.project.projectName)
const handleClick = () => {
console.log('点击了应用卡片', props.project)
getVisitPermission(props.project.projectBizId).then(response => {
getVisitPermission(
props.project.projectBizId,
userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId
).then(response => {
if (response.code === 200) {
// 有权限访问项目,进行跳转
// 记录最近使用的应用
......
......@@ -9,11 +9,7 @@
</template>
<div class="app-list">
<AppCard
v-for="project in projects"
:key="project.projectBizId"
:project="project"
/>
<AppCard v-for="project in projects" :key="project.projectBizId" :project="project" />
</div>
<div v-if="!projects.length" class="empty-container">
......@@ -47,6 +43,7 @@ watch(
// 租户变化时自动刷新
}
)
userStore.getInfo()
</script>
<style lang="scss" scoped>
......
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