Commit ef5c4b4f by qinjianhui

feat:打版管理

parent 28ba2892
......@@ -7,6 +7,8 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
CardWrapper: typeof import('./src/components/CardWrapper.vue')['default']
CommodityCard: typeof import('./src/components/commodityCard.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCol: typeof import('element-plus/es')['ElCol']
......@@ -27,6 +29,8 @@ declare module 'vue' {
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']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
......@@ -37,6 +41,8 @@ declare module 'vue' {
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload']
Icon: typeof import('./src/components/Icon.vue')['default']
ImageView: typeof import('./src/components/ImageView.vue')['default']
LogList: typeof import('./src/components/LogList.vue')['default']
......@@ -45,6 +51,8 @@ declare module 'vue' {
RouterView: typeof import('vue-router')['RouterView']
SplitDiv: typeof import('./src/components/splitDiv/splitDiv.vue')['default']
TableView: typeof import('./src/components/TableView.vue')['default']
UploadImage: typeof import('./src/components/UploadImage.vue')['default']
WangEditor: typeof import('./src/components/WangEditor.vue')['default']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
......
......@@ -11,12 +11,16 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.7",
"dayjs": "^1.11.13",
"element-plus": "^2.6.0",
"lodash-es": "^4.17.21",
"pinia": "^2.1.7",
"splitpanes": "^3.1.5",
"vue": "^3.4.19",
"vue-dompurify-html": "^5.1.0",
"vue-router": "^4.3.0"
},
"devDependencies": {
......
......@@ -55,6 +55,54 @@
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe62e;</span>
<div class="name">查看详情</div>
<div class="code-name">&amp;#xe62e;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe634;</span>
<div class="name">报价查询</div>
<div class="code-name">&amp;#xe634;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe678;</span>
<div class="name">操作日志</div>
<div class="code-name">&amp;#xe678;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6ab;</span>
<div class="name">确认审核【询价】</div>
<div class="code-name">&amp;#xe6ab;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe612;</span>
<div class="name">审核</div>
<div class="code-name">&amp;#xe612;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe600;</span>
<div class="name">审核</div>
<div class="code-name">&amp;#xe600;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe623;</span>
<div class="name">审核</div>
<div class="code-name">&amp;#xe623;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe614;</span>
<div class="name">审核节点</div>
<div class="code-name">&amp;#xe614;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe64a;</span>
<div class="name">超级管理员</div>
<div class="code-name">&amp;#xe64a;</div>
......@@ -84,9 +132,9 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1711355340676') format('woff2'),
url('iconfont.woff?t=1711355340676') format('woff'),
url('iconfont.ttf?t=1711355340676') format('truetype');
src: url('iconfont.woff2?t=1729077025378') format('woff2'),
url('iconfont.woff?t=1729077025378') format('woff'),
url('iconfont.ttf?t=1729077025378') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
......@@ -113,6 +161,78 @@
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-chakanxiangqing"></span>
<div class="name">
查看详情
</div>
<div class="code-name">.icon-chakanxiangqing
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-baojiachaxun"></span>
<div class="name">
报价查询
</div>
<div class="code-name">.icon-baojiachaxun
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-caozuorizhi"></span>
<div class="name">
操作日志
</div>
<div class="code-name">.icon-caozuorizhi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-querenshenhexunjia"></span>
<div class="name">
确认审核【询价】
</div>
<div class="code-name">.icon-querenshenhexunjia
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shenhe"></span>
<div class="name">
审核
</div>
<div class="code-name">.icon-shenhe
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shenhe1"></span>
<div class="name">
审核
</div>
<div class="code-name">.icon-shenhe1
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shenhe2"></span>
<div class="name">
审核
</div>
<div class="code-name">.icon-shenhe2
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shenhe3"></span>
<div class="name">
审核节点
</div>
<div class="code-name">.icon-shenhe3
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-zu54"></span>
<div class="name">
超级管理员
......@@ -159,6 +279,70 @@
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-chakanxiangqing"></use>
</svg>
<div class="name">查看详情</div>
<div class="code-name">#icon-chakanxiangqing</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-baojiachaxun"></use>
</svg>
<div class="name">报价查询</div>
<div class="code-name">#icon-baojiachaxun</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-caozuorizhi"></use>
</svg>
<div class="name">操作日志</div>
<div class="code-name">#icon-caozuorizhi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-querenshenhexunjia"></use>
</svg>
<div class="name">确认审核【询价】</div>
<div class="code-name">#icon-querenshenhexunjia</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shenhe"></use>
</svg>
<div class="name">审核</div>
<div class="code-name">#icon-shenhe</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shenhe1"></use>
</svg>
<div class="name">审核</div>
<div class="code-name">#icon-shenhe1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shenhe2"></use>
</svg>
<div class="name">审核</div>
<div class="code-name">#icon-shenhe2</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shenhe3"></use>
</svg>
<div class="name">审核节点</div>
<div class="code-name">#icon-shenhe3</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-zu54"></use>
</svg>
<div class="name">超级管理员</div>
......
@font-face {
font-family: "iconfont"; /* Project id 4462827 */
src: url('iconfont.woff2?t=1711355340676') format('woff2'),
url('iconfont.woff?t=1711355340676') format('woff'),
url('iconfont.ttf?t=1711355340676') format('truetype');
src: url('iconfont.woff2?t=1729077025378') format('woff2'),
url('iconfont.woff?t=1729077025378') format('woff'),
url('iconfont.ttf?t=1729077025378') format('truetype');
}
.iconfont {
......@@ -13,6 +13,38 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-chakanxiangqing:before {
content: "\e62e";
}
.icon-baojiachaxun:before {
content: "\e634";
}
.icon-caozuorizhi:before {
content: "\e678";
}
.icon-querenshenhexunjia:before {
content: "\e6ab";
}
.icon-shenhe:before {
content: "\e612";
}
.icon-shenhe1:before {
content: "\e600";
}
.icon-shenhe2:before {
content: "\e623";
}
.icon-shenhe3:before {
content: "\e614";
}
.icon-zu54:before {
content: "\e64a";
}
......
......@@ -6,6 +6,62 @@
"description": "",
"glyphs": [
{
"icon_id": "22718987",
"name": "查看详情",
"font_class": "chakanxiangqing",
"unicode": "e62e",
"unicode_decimal": 58926
},
{
"icon_id": "31191570",
"name": "报价查询",
"font_class": "baojiachaxun",
"unicode": "e634",
"unicode_decimal": 58932
},
{
"icon_id": "31891767",
"name": "操作日志",
"font_class": "caozuorizhi",
"unicode": "e678",
"unicode_decimal": 59000
},
{
"icon_id": "5439090",
"name": "确认审核【询价】",
"font_class": "querenshenhexunjia",
"unicode": "e6ab",
"unicode_decimal": 59051
},
{
"icon_id": "5975820",
"name": "审核",
"font_class": "shenhe",
"unicode": "e612",
"unicode_decimal": 58898
},
{
"icon_id": "9346061",
"name": "审核",
"font_class": "shenhe1",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "10190960",
"name": "审核",
"font_class": "shenhe2",
"unicode": "e623",
"unicode_decimal": 58915
},
{
"icon_id": "13938594",
"name": "审核节点",
"font_class": "shenhe3",
"unicode": "e614",
"unicode_decimal": 58900
},
{
"icon_id": "16716546",
"name": "超级管理员",
"font_class": "zu54",
......
......@@ -25,4 +25,7 @@ body,
div {
box-sizing: border-box;
}
li {
list-style: none;
}
</style>
import { BaseRespData } from '@/types/api'
import axios from './axios'
import { LogisticsData } from '@/types/api/order'
import { userData } from '@/types/api/user'
import { VersionImageList } from '@/types/api/typesetting'
// 获取物流公司
export function getLogisticsCompanyList() {
......@@ -8,3 +10,22 @@ export function getLogisticsCompanyList() {
'factory/customJomallOrder/getLogisticsList',
)
}
// 获取用户
export function getUserListApi() {
return axios.get<never, BaseRespData<userData[]>>('factory/factoryUser/list')
}
// 上传图片文件
export function uploadImageApi(data: FormData) {
return axios.post<never, BaseRespData<never> & VersionImageList>(
'upload/ossUpload',
data,
)
}
export function uploadFileApi(data: FormData) {
return axios.post<never, BaseRespData<VersionImageList>>(
'supply/supplyTypesettingInfo/uploadManuscript',
data,
)
}
import axios from './axios'
import { BasePaginationData, BaseRespData } from '@/types/api'
import {
TypesettingListData,
SearchForm,
LogList,
} from '@/types/api/typesetting'
import type {
PriceForm,
TreeData,
VersionImageList,
VersionList,
} from '@/types/api/typesetting'
export function getTypeListApi(
data: SearchForm,
currentPage: number,
pageSize: number,
) {
return axios.post<never, BasePaginationData<TypesettingListData>>(
'supply/supplyTypesettingInfo/list_page',
{ ...data, currentPage, pageSize },
)
}
export function getStatusListApi() {
return axios.get<never, BaseRespData<TreeData[]>>(
'supply/supplyTypesettingInfo/findStateGroupList',
)
}
export function getTypesettingDetail(id: number) {
return axios.get<never, BaseRespData<never>>(
'supply/supplyTypesettingInfo/get',
{
params: {
id,
},
},
)
}
export function saveTypesettingReviewApi(data: TypesettingListData) {
return axios.post<never, BaseRespData<never>>(
'supply/supplyTypesettingInfo/supplierConfirm',
data,
)
}
export function submitReviewApi(
typesettingId: number,
version: number,
examineImages: VersionImageList[],
) {
return axios.post<never, BaseRespData<never>>(
'supply/supplyTypesettingExamine/submitExamine',
{
typesettingId,
version,
examineImages,
},
)
}
export function getExamineInfoByIdApi(typesettingId: number) {
return axios.get<never, BaseRespData<VersionList[]>>(
'supply/supplyTypesettingExamine/getByTypesettingId',
{
params: {
typesettingId,
},
},
)
}
export function submitReviewOpinionApi(
typesettingId: number,
examine: boolean,
opinion: string,
id?: number,
) {
return axios.post<never, BaseRespData<never>>(
'supply/supplyTypesettingExamine/examine',
{
typesettingId,
examine,
opinion,
id,
},
)
}
export function submitReviewPriceInfoApi(id: number, data: PriceForm) {
return axios.post<never, BaseRespData<never>>(
'supply/supplyTypesettingPrice/saveQuotedPrice',
{
typesettingId: id,
...data,
},
)
}
export function getTypesettingLogByIdApi(id: number) {
return axios.get<never, BaseRespData<LogList[]>>(
'supply/supplyTypesettingInfo/getLogList',
{
params: { id },
},
)
}
export function rejectDataApi(ids: string) {
return axios.get<never, BaseRespData<never>>(
'supply/supplyTypesettingInfo/batchReject',
{
params: {
ids,
},
},
)
}
export function getPriceDetailApi(id: number) {
return axios.get<never, BaseRespData<PriceForm>>(
'supply/supplyTypesettingPrice/getPriceDetail',
{
params: {
id,
},
},
)
}
<template>
<div class="commodity-card">
<div class="commodity-card-image">
<div class="before"></div>
<div class="image">
<img :src="cardItem.mainImage" />
</div>
<div class="commodity-card-image-select">
<span class="select-icon"></span>
</div>
<div class="operation-icon">
<slot name="operations"></slot>
</div>
</div>
<div class="commodity-card-info">
<div class="color-image flex flex-gap-10">
<div
v-for="c in cardItem.imageList"
:key="c.id"
class="color-image-item"
>
<img :src="c.imagePath" />
</div>
</div>
<div class="commodity-card-name-price flex flex-justify-space-between">
<slot name="productName"></slot>
<slot name="price"></slot>
</div>
<div class="commodity-card-sku" @click.stop="copy(cardItem.sku ?? '')">
<img src="../assets/images/id.png" height="20" />
<span>{{ cardItem.sku }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { PropType } from 'vue'
import type { TypesettingListData } from '../types/api/typesetting'
defineProps({
cardItem: {
type: Object as PropType<TypesettingListData>,
required: true,
},
})
const copy = (text: string) => {
navigator.clipboard.writeText(text)
ElMessage.success('复制成功')
}
</script>
<style lang="scss" scoped>
.commodity-card-image {
position: relative;
}
.before {
height: 0;
padding-top: 120%;
}
.image {
position: absolute;
inset: 0;
img {
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
}
}
.color-image {
.color-image-item {
width: 50px;
height: 50px;
}
img {
width: 100%;
height: 100%;
}
}
.commodity-card-info {
padding: 10px;
font-size: 14px;
background: linear-gradient(
90deg,
rgba(228, 228, 228, 0.4) 0%,
rgba(255, 255, 255, 1) 100%
);
}
.commodity-card-name-price {
height: 30px;
line-height: 30px;
}
.commodity-card-sku {
display: flex;
align-items: center;
gap: 5px;
background-color: #fff;
}
.commodity-card-image-select {
position: absolute;
top: 5px;
left: 5px;
}
.select-icon {
position: relative;
display: block;
width: 16px;
height: 16px;
border: 1px solid #cccccc;
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.4) inset;
border-radius: 8px;
background: #fff;
}
.commodity-card.active .select-icon {
background: #4168ff;
position: relative;
border-color: #4168ff;
box-shadow: none;
font-size: 18px;
top: -1px;
left: -1px;
}
.commodity-card.active .select-icon::after {
position: absolute;
content: '';
top: 0px;
left: 4px;
width: 4px;
height: 8px;
border-width: 2px;
border-style: solid;
border-color: transparent #fff #fff transparent;
transform: rotate(45deg) scale(0.8);
}
.operation-icon {
position: absolute;
bottom: 5px;
right: 5px;
:deep(.svg-icon) {
width: 2em;
height: 2em;
}
}
</style>
<template>
<div v-loading="loading" class="upload-file">
<input
ref="fileInputRef"
type="file"
style="display: none"
@change="onChange"
/>
<div v-if="imageMode" class="image-wrap" @click="onFileClick">
<img v-if="value" :src="value" alt="" />
<el-icon v-else><Plus /></el-icon>
</div>
<template v-else>
<div v-if="(files && files.length) < max" class="upload-file-add">
<el-icon @click="fileInputRef.click()"><Plus /></el-icon>
</div>
<ul class="files">
<li
v-for="(item, i) in files"
:key="item.path"
class="upload-files-file"
>
<span
:title="`点击下载 ${item.path} 文件`"
class="filename"
@click="downloadFile(item)"
>
{{ item?.filename || filename(item.path) }}
</span>
<el-icon v-if="!disabled" style="cursor: pointer" @click="removeFile(i)"
><Close
/></el-icon>
</li>
</ul>
</template>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { Plus, Close } from '@element-plus/icons-vue'
import { uploadImageApi, uploadFileApi } from '@/api/common'
import { getFilePath } from '@/api/axios'
const props = defineProps({
modelValue: String,
disabled: Boolean,
imageMode: {
type: Boolean,
default: false,
},
max: {
type: Number,
default: 1,
},
text: String,
})
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(val) {
emit('update:modelValue', val)
},
})
const files = ref<FileItem[]>([])
watch(
value,
() => {
let file = []
if (!Array.isArray(value)) {
if (value.value) {
file.push({ path: value.value })
} else {
file = []
}
}
files.value = file
},
{ immediate: true },
)
const fileInputRef = ref()
const loading = ref(false)
const onChange = async (e: Event) => {
const files = (e.target as HTMLInputElement).files
if (!files) return
const file = files[0]
if (props.imageMode) {
imageUpload(file)
} else {
fileUpload(file)
}
fileInputRef.value.value = ''
}
interface FileItem {
path: string
filename?: string
}
const fileUpload = async (file: File) => {
const formData = new FormData()
const filename = file.name
formData.append('file', file)
formData.append('businessType', 'product')
loading.value = true
try {
const res = await uploadFileApi(formData)
files.value.push({ path: res.message ?? '', filename })
value.value = res.message
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const imageUpload = async (file: File) => {
const formData = new FormData()
formData.append('file', file)
formData.append('businessType', 'product')
loading.value = true
try {
const res = await uploadImageApi(formData)
value.value = res.filePath
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const onFileClick = () => {
if (props.disabled) return
fileInputRef.value.click()
}
const filename = (path: string) => {
if (!path) return ''
const i = path.lastIndexOf('/')
if (i === -1) return path
return path.substring(i + 1)
}
const downloadFile = (item: FileItem) => {
window.open(getFilePath() + item.path)
}
const removeFile = (i: number) => {
files.value.splice(i, 1)
value.value = ''
}
// const _lastValue = ref()
// const emitValue = () => {
// let v
// if (props.max === 1) {
// v = files.value[0] && files.value[0].path
// } else {
// v = files.value.map((e) => e.path)
// }
// _lastValue.value = v
// emit('update:modelValue', v)
// }
</script>
<style scoped lang="scss">
ul,
li {
margin: 0;
padding: 0;
}
.upload-file {
width: 100%;
}
.image-wrap {
width: 100%;
height: 200px;
width: 100%;
border: 1px solid #dcdfe6;
display: flex;
align-items: center;
justify-content: center;
i {
font-size: 36px;
color: #777;
cursor: pointer;
}
img {
height: 100%;
width: 100%;
object-fit: contain;
}
}
.upload-file-add {
height: 100px;
border: 1px solid #dcdfe6;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
.el-icon {
font-size: 36px;
color: #777;
}
}
.upload-files-file {
display: flex;
align-items: center;
justify-content: space-between;
}
.filename {
cursor: pointer;
}
</style>
<template>
<div style="border: 1px solid #ccc; z-index: 9999">
<Toolbar
class="editor_toolbar"
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:default-config="toolbarConfig"
:mode="mode"
/>
<Editor
v-model="html"
style="height: 300px; overflow-y: hidden"
:default-config="editorConfig"
:mode="mode"
@on-created="onCreated"
/>
</div>
</template>
<script setup lang="ts">
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css'
import { computed, onBeforeUnmount, ref, shallowRef } from 'vue'
import { uploadImg } from '@/utils/file'
const props = defineProps({
modelValue: String,
placeholder: {
type: String,
default: '',
},
isInsert: {
type: Boolean,
default: false,
},
insertData: {
type: Array,
default: () => [
{
label: '客户名称',
value: '{payerName} {payerSurname}',
},
{ label: '订单编号', value: '{id}' },
{ label: '店铺单号', value: '{shopNumber}' },
{ label: '商品名称', value: '{productName}' },
{ label: '商品规格', value: '{productSpec}' },
{ label: '商品数量', value: '{productQuantity}' },
{ label: '商品单价', value: '{productPrice}' },
],
},
})
const emit = defineEmits(['update:modelValue'])
const html = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
},
})
const editorRef = shallowRef()
const toolbarConfig = computed(() => {
return {
excludeKeys: ['group-video'],
}
})
const editorConfig = computed(() => {
return {
placeholder: props.placeholder,
MENU_CONF: {
uploadImage: {
allowedFileTypes: ['jpg', 'jpeg', 'png', 'gif', 'bmp'],
async customUpload(files: File, insertFn: (url: string) => void) {
// JS 语法
// res 即服务端的返回结果
const res = await uploadImg(files, {
url: 'upload/ossUpload',
businessType: 'product',
})
const url = res.imagePath || res.filePath
// 从 res 中找到 url alt href ,然后插入图片
if (url) insertFn(url)
},
},
insertSelect: { options: props.insertData },
},
}
})
const mode = ref('default')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onCreated = (editor: any) => {
console.log(editor)
editorRef.value = editor
}
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
</script>
<style scoped>
.editor_toolbar::v-deep .panel_menu li:hover {
background: #efefef;
}
</style>
......@@ -4,5 +4,6 @@ import App from './App.vue'
import router from './router'
import store from './store'
import './styles/index.scss'
import vueDomPurifyHTMLPlugin from 'vue-dompurify-html'
createApp(App).use(router).use(store).mount('#app')
createApp(App).use(vueDomPurifyHTMLPlugin).use(router).use(store).mount('#app')
......@@ -16,6 +16,7 @@ import { getToken } from '@/api/axios'
import UserPage from '@/views/UserPage.vue'
import DeliveryNotePage from '@/views/DeliveryNotePage.vue'
import AccountStatementNote from '@/views/AccountStatementNote.vue'
import TypeseetingManagement from '@/views/typesetting/TypesettingManagement.vue'
const router = createRouter({
history: createWebHistory(),
......@@ -49,6 +50,10 @@ const router = createRouter({
path: '/account/statement-note',
component: AccountStatementNote,
},
{
path:'/typesetting-management/list',
component: TypeseetingManagement,
}
],
},
// 登录
......
......@@ -25,8 +25,13 @@ const menu: MenuItem[] = [
label: '发货单',
},
{
index: '',
index: '/typesetting-management/list',
id: 5,
label:'打版管理'
},
{
index: '',
id: 6,
label: '系统设置',
children: [
{
......
......@@ -50,6 +50,10 @@ img {
flex: 1;
}
.flex-gap-10 {
gap: 10px;
}
.font-bold {
font-weight: bold;
}
......
export interface TreeData {
statusName: string
children?: TreeData[]
statusCode: string
quantity?: number
}
export interface TypesettingListData {
id: number
sku?: string
homeSku?: string
productName?: string
mainImage?: string
categoryId?: number
status?: string
state?: number | string
type?: number
shelve?: number
printType?: number
scImgType?: number
openly?: number
imageList?: ImageListData[]
retailPrice?: number
urgentLevel?: number
expectCompleteTime?: string
promiseCompleteTime?: string
requirement?: string
confirm?: boolean
personInChargeId?: number
personInChargeName?: string
}
export interface SearchForm {
pageSize: number
currentPage: number
sku: string
state?: string
}
export interface EmergencyLevel {
name: string
desc: string
value: number
}
export interface VersionImageList {
imagePath?: string
filePath?: string
sort: number
version?: string
}
export interface VersionList {
id?: number
typesettingId?: number
opinion?: string
examinePeopleId?: string
examinePeopleName?: string
state?: number
version?: number
createTime?: string
updateTime?: string
examineImages: ExamineImage[]
examine?: string
}
export interface ExamineImage {
id: number
typesettingId: number
typesettingExamineId?: number
imagePath: string
zimagePath?: string
sort: number
createTime?: string
updateTime?: string
}
export interface PriceForm {
id?: number
supplyTypesettingId?: number
craftImage?: string
bulkUnitPrice?: string
testUnitPrice?: string
deliveryDay?: number
priceOpinion?: string
priceNote?: string
updateTime?: string
createTime?: string
flag?: boolean
}
export interface LogList {
id: number
supplyTypesettingId: number
supplyTypesettingStatus: string
employeeId: number
description: string
createdTime: string
}
export interface ImageListData {
id: number
imagePath: string
filePath?: string
sort: number
}
import axios from '@/api/axios'
import { BaseRespData } from '@/types/api'
import { VersionImageList } from '@/types/api/typesetting'
export function getFilePath(fileName?: string) {
return import.meta.env.BASE_URL + fileName
}
\ No newline at end of file
}
// 上传
export function uploadImg(
file: File,
params: { url?: string } & Record<string, string | Blob> = {},
) {
const from = new FormData()
from.append('file', file)
const { url, ...otherParams } = params
for (const key in otherParams) {
from.append(key, otherParams[key])
}
return axios.post<never, BaseRespData<void> & VersionImageList>(
url || `upload/ossUpload`,
from,
{
headers: { 'content-type': 'multipart/form-data' },
},
)
}
<template>
<ElDialog
v-model="visible"
:title="title"
width="1400px"
top="6vh"
:close-on-click-modal="false"
>
<div v-if="row" class="detail-info">
<div class="review-image">
<ul class="color-image">
<li
v-for="(val, index) in imageList"
:key="index"
class="color-image-item"
>
<!-- <button class="remove" type="button">
<i class="el-icon-close"></i>
</button> -->
<img loading="lazy" :src="val.imagePath" alt="" />
</li>
<!-- <li>
<div class="color-image-item" @click="upload">
<el-icon><Plus /></el-icon>
</div>
</li> -->
</ul>
<div class="main-image">
<img :src="row?.mainImage" />
</div>
</div>
<div class="review-from">
<ElForm
ref="reviewFormRef"
:model="reviewForm"
label-width="120px"
inline
>
<ElFormItem label="编码" prop="sku">
<ElInput
v-model="reviewForm.sku"
disabled
placeholder="编码"
style="width: 200px"
/>
</ElFormItem>
<ElFormItem label="负责人" prop="personInChargeId">
<ElSelect
v-model="reviewForm.personInChargeId"
style="width: 200px"
clearable
disabled
placeholder="请选择负责人"
>
<ElOption
v-for="item in userList"
:key="item.id"
:label="item.account"
:value="item.id"
></ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="紧急程度" prop="urgentLevel">
<el-radio-group
v-model="reviewForm.urgentLevel"
style="width: 200px"
>
<el-radio
v-for="(item, index) in emergencyLevel"
:key="index"
disabled
:label="item.value"
>
<svg
t="1627697200171"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="629"
width="18"
height="18"
>
<path
:fill="setColor(index)"
d="M444.970667 850.090667l151.978666-64.512a85.290667 85.290667 0 0 1-151.978666 64.512z m231.765333-760.746667c89.002667 48.597333 155.818667 111.018667 199.765333 187.178667 43.946667 76.117333 64.64 165.205333 62.250667 266.581333a32 32 0 0 1-64-1.536c2.133333-90.069333-15.872-167.552-53.674667-233.045333-37.802667-65.536-95.957333-119.850667-175.018666-163.029334a32 32 0 1 1 30.72-56.192z m-390.4 188.629333a248.490667 248.490667 0 0 1 324.266667 117.162667l4.181333 8.917333 54.954667 123.392 72.448 62.72c3.968 3.413333 7.381333 7.381333 11.349333 13.824l2.517333 4.906667a53.333333 53.333333 0 0 1-27.178666 70.357333L288.213333 874.24A53.333333 53.333333 0 0 1 213.333333 825.472l-0.042666-99.968-52.992-119.082667A248.405333 248.405333 0 0 1 286.293333 277.973333z m380.288-34.986666c41.258667 22.442667 74.325333 55.210667 98.816 97.621333 24.490667 42.453333 36.309333 87.466667 35.157333 134.442667a32 32 0 1 1-64-1.578667c0.853333-35.029333-7.893333-68.437333-26.581333-100.864-18.688-32.384-43.264-56.661333-74.026667-73.472a32 32 0 0 1 30.634667-56.192z"
p-id="630"
></path>
</svg>
</el-radio>
</el-radio-group>
</ElFormItem>
<ElFormItem label="期望完成日期" prop="expectCompleteTime">
<ElDatePicker
v-model="reviewForm.expectCompleteTime"
disabled
placeholder="期望完成日期"
style="width: 200px"
></ElDatePicker>
</ElFormItem>
<ElFormItem
label="承诺完成日期"
prop="promiseCompleteTime"
:rules="[
{
required: true,
message: '请选择承诺完成日期',
},
]"
>
<ElDatePicker
v-model="reviewForm.promiseCompleteTime"
style="width: 200px"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="承诺完成日期"
></ElDatePicker>
</ElFormItem>
<p
v-dompurify-html="reviewForm.requirement"
style="height: 470px"
class="requirement"
></p>
</ElForm>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button size="large" @click="visible = false">取消</el-button>
<el-button size="large" type="primary" @click="saveReview(true)"
>保存
</el-button>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { TypesettingListData, EmergencyLevel,ImageListData } from '@/types/api/typesetting'
import {
computed,
defineEmits,
defineProps,
PropType,
ref,
onMounted,
} from 'vue'
import { saveTypesettingReviewApi } from '@/api/typesetting'
import { getUserListApi } from '@/api/common'
import { userData } from '@/types/api/user'
// import { Plus } from '@element-plus/icons-vue'
import { showConfirm } from '@/utils/ui'
const props = defineProps({
row: {
type: Object as PropType<TypesettingListData>,
default: () => {},
},
modelValue: Boolean,
title: String,
})
const emit = defineEmits(['update:modelValue', 'update:modelValue', 'refresh'])
const visible = computed({
get() {
return props.modelValue
},
set(val: boolean) {
emit('update:modelValue', val)
},
})
const reviewForm = computed({
get() {
return props.row
},
set(val: TypesettingListData) {
emit('update:modelValue', val)
},
})
const emergencyLevel = ref<EmergencyLevel[]>([])
emergencyLevel.value = [
{
name: 'VERY_URGENT',
desc: '非常紧急',
value: 3,
},
{
name: 'URGENT',
desc: '紧急',
value: 2,
},
{
name: 'GENERAL',
desc: '一般',
value: 1,
},
]
const setColor = (index: number) => {
if (index == 0) {
return '#F54D66'
}
if (index == 1) {
return '#FDC834'
}
if (index == 2) {
return '#5DE088'
}
}
const reviewFormRef = ref()
const saveReview = async (bol: boolean) => {
if (!bol) {
try {
await showConfirm('是否确认驳回', {
type: 'warning',
confirmButtonText: '确认',
cancelButtonText: '取消',
})
} catch {
return
}
}
try {
await reviewFormRef.value.validate()
} catch {
return
}
const personInChargeName = userList.value.find(
(item) => item.id === reviewForm.value.personInChargeId,
)?.account
try {
const res = await saveTypesettingReviewApi({
...reviewForm.value,
personInChargeName,
confirm: bol,
})
ElMessage.success(res.message)
emit('update:modelValue', false)
emit('refresh')
} catch (e) {
console.error(e)
}
}
const refreshForm = () => {
reviewFormRef.value.resetFields()
}
const userList = ref<userData[]>([])
const getUserList = async () => {
try {
const res = await getUserListApi()
userList.value = res.data
} catch (e) {
console.error(e)
}
}
onMounted(() => {
getUserList()
})
defineExpose({
refreshForm,
})
const imageList = computed<ImageListData[]>(() => {
if (!props.row) return []
return props.row?.imageList ?? []
})
// const upload = () => {}
</script>
<style lang="scss" scoped>
ul,li {
margin: 0;
padding: 0;
}
.color-image-item {
width: 58px;
height: 58px;
border: 1px solid #ddd;
cursor: pointer;
position: relative;
margin: 5px auto;
img {
width: 100%;
height: 100%;
}
}
.detail-info {
display: flex;
gap: 10px;
}
.review-image {
width: 40%;
display: flex;
gap: 10px;
}
.review-from {
width: 60%;
}
.requirement {
border: 1px solid #ddd;
}
.main-image {
border: 1px solid #ddd;
flex: 1;
display: flex;
align-items: center;
img {
height: 100%;
width: 100%;
object-fit: contain;
}
}
.dialog-footer {
text-align: center;
}
</style>
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