Browse Source

(+) 二维码基本功能实现

master
liujiangyong 9 months ago
parent
commit
c12a43acdc
  1. 71
      api/app/lib/controllers/label/index.js
  2. 173
      api/app/lib/controllers/publicityInfo/index.js
  3. 56
      api/app/lib/controllers/qrcode/index.js
  4. 16
      api/app/lib/index.js
  5. 11
      api/app/lib/middlewares/authenticator.js
  6. 74
      api/app/lib/models/publicity_info.js
  7. 70
      api/app/lib/models/qrcode.js
  8. 87
      api/app/lib/models/qrcode_files.js
  9. 34
      api/app/lib/models/qrcode_labels.js
  10. 64
      api/app/lib/models/qrcode_labels_qrcode.js
  11. 15
      api/app/lib/routes/label/index.js
  12. 18
      api/app/lib/routes/publicityInfo/index.js
  13. 13
      api/app/lib/routes/qrcode/index.js
  14. 47
      script/1.0.0/schema/2.init_qrcode.sql
  15. 1
      web/client/assets/images/document.svg
  16. BIN
      web/client/assets/images/logo.png
  17. 9
      web/client/assets/images/logo.svg
  18. BIN
      web/client/assets/images/logo_white.png
  19. 5
      web/client/index.ejs
  20. 7
      web/client/index.html
  21. 52
      web/client/src/components/QrcodeFilesUpload/fileInfoForm.js
  22. 413
      web/client/src/components/QrcodeFilesUpload/index.js
  23. 0
      web/client/src/components/QrcodeFilesUpload/index.less
  24. 2
      web/client/src/layout/components/header/index.js
  25. 22
      web/client/src/sections/organization/actions/authority.js
  26. 2
      web/client/src/sections/publicityInfoConfig/actions/index.js
  27. 38
      web/client/src/sections/publicityInfoConfig/actions/label.js
  28. 79
      web/client/src/sections/publicityInfoConfig/actions/publicityInfoConfig.js
  29. 273
      web/client/src/sections/publicityInfoConfig/components/publicityInfoModal.js
  30. 145
      web/client/src/sections/publicityInfoConfig/containers/publicityInfoConfig.js
  31. 89
      web/client/src/sections/qrCode/actions/qrCode.js
  32. 3
      web/client/src/sections/qrCode/containers/index.js
  33. 18
      web/client/src/sections/qrCode/containers/qrCode.js
  34. 141
      web/client/src/sections/qrCode/containers/scanCode.js
  35. 16
      web/client/src/sections/qrCode/containers/scanCode.less
  36. 47
      web/client/src/sections/qrCode/routes.js
  37. 131
      web/client/src/utils/webapi.js
  38. 4
      web/package.json
  39. 11
      web/webpack.config.js
  40. 13
      web/webpack.config.prod.js

71
api/app/lib/controllers/label/index.js

@ -0,0 +1,71 @@
'use strict';
async function getLabels(ctx, next) {
try {
const models = ctx.fs.dc.models;
const res = await models.QrcodeLabels.findAll({
order: [['id', 'DESC']],
})
ctx.status = 200;
ctx.body = res
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "获取标签失败"
}
}
}
async function createLabels(ctx, next) {
const models = ctx.fs.dc.models;
try {
let rslt = ctx.request.body;
await models.Department.create(Object.assign({}, rslt, { read: 1 }))
ctx.status = 204;
ctx.body = { message: '新建部门成功' }
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '新建部门失败' }
}
}
async function delLabels(ctx, next) {
let errMsg = "删除部门失败";
try {
const models = ctx.fs.dc.models;
const { id } = ctx.params;
let list = await models.Department.findAll({});
let deptIds = list.map(l => l.id);
const allUsers = await models.User.findAll({
where: {
departmentId: { $in: deptIds },
delete: false
}
})
const childrenDept = await models.Department.findAll({ where: { dependence: id } })
const childrenUser = allUsers.filter(au => au.departmentId == id);
if (childrenUser.length || childrenDept.length) {
errMsg = '请先删除部门下的用户或子部门';
throw errMsg;
}
await models.Department.destroy({ where: { id: id } });
await models.Department.destroy({ where: { dependence: id } });
ctx.status = 204;
ctx.body = { message: '删除部门成功' }
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: error }
}
}
module.exports = {
getLabels,
createLabels,
delLabels,
}

173
api/app/lib/controllers/publicityInfo/index.js

@ -0,0 +1,173 @@
'use strict';
const Hex = require('crypto-js/enc-hex');
const MD5 = require('crypto-js/md5');
async function getPublicityInfo(ctx, next) {
try {
const models = ctx.fs.dc.models;
const { limit, page, name, label } = ctx.query
let findOption = {
where: {},
order: [['time', 'DESC']],
include: [
{
model: models.Qrcode,
},
{
model: models.QrcodeLabels,
attributes: ["name"],
},
{
model: models.QrcodeFiles,
},
],
distinct: true
}
if (limit) {
findOption.limit = Number(limit)
}
if (page && limit) {
findOption.offset = page * limit
}
// if (startTime && endTime) {
// findOption.where.time = { $between: [startTime, endTime] };
// }
if (name) {
findOption.where.name = { $like: `%${name}%` }
}
const res = await models.PublicityInfo.findAndCountAll(findOption)
ctx.status = 200;
ctx.body = res
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "获取宣传信息失败"
}
}
}
async function createPublicityInfo(ctx, next) {
const models = ctx.fs.dc.models;
const transaction = await ctx.fs.dc.orm.transaction();
try {
const body = ctx.request.body;
const { type, name, labels, qrcode, files } = body;
const dbQrcode = await models.Qrcode.create({
name, type, url: qrcode.url, key: qrcode.key, logo: qrcode.logo,
}, { transaction });
const dbPublicityInfo = await models.PublicityInfo.create({
...body,
qrcodeId: dbQrcode.id,
time: new Date().getTime(),
}, { transaction });
if (labels && labels.length) {
let labelIds = []
let createLabels = [];
for (const l of labels) {
if (typeof l === 'string') {
createLabels.push({ name: l });
} else {
labelIds.push(l);
}
}
if (createLabels.length) {
const dbLabels = await models.QrcodeLabels.bulkCreate(createLabels, { returning: true, transaction });
labelIds = labelIds.concat(dbLabels.map(l => l.id));
}
if (labelIds.length) {
await models.QrcodeLabelsQrcode.bulkCreate(labelIds.map(l => ({
labelId: l, qrcodeId: dbQrcode.id, publicityInfoId: dbPublicityInfo.id
})), { transaction });
}
}
if (type !== '链接') {
if (!files || !files.length) throw new Error('缺少请求参数: files');
await models.QrcodeFiles.bulkCreate(files.map(f => ({
fileName: f.customFileName ? f.customFileName : f.name.split('.').slice(0, -1).join('.'),
fileSize: f.size,
fileUrl: f.storageUrl,
previewImgUrl: f.previewImgUrl,
qrcodeId: dbQrcode.id,
publicityInfoId: dbPublicityInfo.id,
})), { transaction });
}
ctx.status = 204;
ctx.body = { message: '新建宣传信息成功' };
await transaction.commit();
} catch (error) {
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '新建宣传信息失败' };
}
}
async function updatePublicityInfo(ctx, next) {
try {
const models = ctx.fs.dc.models;
const { id } = ctx.params;
const body = ctx.request.body;
await models.Department.update(
body,
{ where: { id: id } }
)
ctx.status = 204;
ctx.body = { message: '修改部门成功' }
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '修改部门失败' }
}
}
async function delPublicityInfo(ctx, next) {
const transaction = await ctx.fs.dc.orm.transaction();
let errMsg = "删除宣传信息失败";
try {
const models = ctx.fs.dc.models;
const { id } = ctx.params;
const { qrcodeId } = ctx.query;
await models.QrcodeLabelsQrcode.destroy({
where: { publicityInfoId: id },
transaction
});
await models.QrcodeFiles.destroy({
where: { publicityInfoId: id },
transaction
});
await models.PublicityInfo.destroy({
where: { id: id },
transaction: transaction,
});
await models.Qrcode.destroy({
where: { id: qrcodeId },
transaction
});
ctx.status = 204;
ctx.body = { message: '删除宣传信息成功' }
transaction.commit();
} catch (error) {
transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: error }
}
}
module.exports = {
getPublicityInfo,
createPublicityInfo,
updatePublicityInfo,
delPublicityInfo,
}

56
api/app/lib/controllers/qrcode/index.js

@ -0,0 +1,56 @@
'use strict';
async function getQrcode(ctx, next) {
try {
const models = ctx.fs.dc.models;
const { limit, page, name, key } = ctx.query
let options = {
where: {},
order: [['id', 'DESC']],
include: []
}
if (name) {
options.where.name = { $like: `%${name}%` }
}
if (key) {
options.where.key = key
options.include.push({
model: models.QrcodeFiles,
})
options.distinct = true
}
const res = await models.Qrcode.findAndCountAll(options)
ctx.status = 200;
ctx.body = res
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "获取二维码失败"
}
}
}
async function createQrcode(ctx, next) {
const models = ctx.fs.dc.models;
try {
let rslt = ctx.request.body;
const qrcode = await models.Qrcode.create(rslt)
ctx.status = 204;
ctx.body = { message: '新增二维码成果', id: qrcode.id }
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = { message: '新增二维码失败' }
}
}
module.exports = {
getQrcode,
createQrcode,
}

16
api/app/lib/index.js

@ -53,7 +53,10 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq
require(`./models/${filename}`)(dc)
});
const { Department, User, UserResource, Resource, } = dc.models;
const {
Department, User, UserResource, Resource, Qrcode, QrcodeFiles, QrcodeLabels,
QrcodeLabelsQrcode, PublicityInfo,
} = dc.models;
UserResource.belongsTo(User, { foreignKey: 'userId', targetKey: 'id' });
User.hasMany(UserResource, { foreignKey: 'userId', sourceKey: 'id' });
@ -65,4 +68,15 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq
User.belongsTo(Department, { foreignKey: 'departmentId', targetKey: 'id' });
Department.hasMany(User, { foreignKey: 'departmentId', sourceKey: 'id' });
Qrcode.belongsToMany(QrcodeLabels, { through: QrcodeLabelsQrcode, foreignKey: 'qrcodeId', otherKey: 'labelId' });
QrcodeLabels.belongsToMany(Qrcode, { through: QrcodeLabelsQrcode, foreignKey: 'labelId', otherKey: 'qrcodeId' });
PublicityInfo.belongsToMany(QrcodeLabels, { through: QrcodeLabelsQrcode, foreignKey: 'publicityInfoId', otherKey: 'labelId' });
QrcodeLabels.belongsToMany(PublicityInfo, { through: QrcodeLabelsQrcode, foreignKey: 'labelId', otherKey: 'publicityInfoId' });
QrcodeFiles.belongsTo(Qrcode, { foreignKey: 'qrcodeId', targetKey: 'id' });
Qrcode.hasMany(QrcodeFiles, { foreignKey: 'qrcodeId', sourceKey: 'id' });
QrcodeFiles.belongsTo(PublicityInfo, { foreignKey: 'publicityInfoId', targetKey: 'id' });
PublicityInfo.hasMany(QrcodeFiles, { foreignKey: 'publicityInfoId', sourceKey: 'id' });
PublicityInfo.belongsTo(Qrcode, { foreignKey: 'qrcodeId', targetKey: 'id' });
Qrcode.hasMany(PublicityInfo, { foreignKey: 'qrcodeId', sourceKey: 'id' });
};

11
api/app/lib/middlewares/authenticator.js

@ -13,13 +13,13 @@ class ExcludesUrls {
this.reload(opts);
}
sanitizePath (path) {
sanitizePath(path) {
if (!path) return '/';
const p = '/' + path.replace(/^\/+/i, '').replace(/\/+$/, '').replace(/\/{2,}/, '/');
return p;
}
reload (opts) {
reload(opts) {
// load all url
if (!this.allUrls) {
this.allUrls = opts;
@ -37,7 +37,7 @@ class ExcludesUrls {
}
}
isExcluded (path, method) {
isExcluded(path, method) {
return this.allUrls.some(function (url) {
return !url.auth
&& url.pregexp.test(path)
@ -59,6 +59,7 @@ let isPathExcluded = function (opts, path, method) {
let excludeOpts = opts.exclude || [];
excludeOpts.push({ p: '/login', o: 'POST' });
excludeOpts.push({ p: '/logout', o: 'PUT' });
excludeOpts.push({ p: '/qrcode', o: 'GET' });
excludes = new ExcludesUrls(excludeOpts);
}
let excluded = excludeAll || excludes.isExcluded(path, method);
@ -110,8 +111,8 @@ let isResourceAvailable = function (resources, options) {
return !authCode || (resources || []).some(code => code === authCode);
};
function factory (app, opts) {
return async function auth (ctx, next) {
function factory(app, opts) {
return async function auth(ctx, next) {
const { path, method, header, query } = ctx;
ctx.fs.logger.log('[AUTH] start', path, method);
ctx.fs.api = ctx.fs.api || {};

74
api/app/lib/models/publicity_info.js

@ -0,0 +1,74 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const PublicityInfo = sequelize.define("publicityInfo", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "宣传标题",
primaryKey: false,
field: "name",
autoIncrement: false,
},
type: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "类型(图片|文件|链接|视频)",
primaryKey: false,
field: "type",
autoIncrement: false,
},
time: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: "创建/更新时间",
primaryKey: false,
field: "time",
autoIncrement: false,
},
qrcodeId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: "关联二维码ID",
primaryKey: false,
field: "qrcode_id",
autoIncrement: false,
references: {
model: "qrcode",
key: "id"
}
},
link: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "type为链接时的链接地址",
primaryKey: false,
field: "link",
autoIncrement: false,
},
}, {
tableName: "publicity_info",
comment: "",
indexes: []
});
dc.models.PublicityInfo = PublicityInfo;
return PublicityInfo;
};

70
api/app/lib/models/qrcode.js

@ -0,0 +1,70 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const Qrcode = sequelize.define("qrcode", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
},
url: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "二维码图片存储链接",
primaryKey: false,
field: "url",
autoIncrement: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "二维码名称",
primaryKey: false,
field: "name",
autoIncrement: false,
},
type: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "类型(图片|文件|链接|视频)",
primaryKey: false,
field: "type",
autoIncrement: false,
},
key: {
type: DataTypes.UUID,
allowNull: false,
defaultValue: null,
comment: "二维码唯一标识",
primaryKey: false,
field: "key",
autoIncrement: false,
},
logo: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "二维码中间的照片",
primaryKey: false,
field: "logo",
autoIncrement: false,
},
}, {
tableName: "qrcode",
comment: "",
indexes: []
});
dc.models.Qrcode = Qrcode;
return Qrcode;
};

87
api/app/lib/models/qrcode_files.js

@ -0,0 +1,87 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const QrcodeFiles = sequelize.define("qrcodeFiles", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
},
fileName: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "文件名",
primaryKey: false,
field: "file_name",
autoIncrement: false,
},
fileSize: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "文件大小(byte)",
primaryKey: false,
field: "file_size",
autoIncrement: false,
},
fileUrl: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "文件存储链接",
primaryKey: false,
field: "file_url",
autoIncrement: false,
},
previewImgUrl: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "文件预览图链接",
primaryKey: false,
field: "preview_img_url",
autoIncrement: false,
},
qrcodeId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: "关联二维码ID",
primaryKey: false,
field: "qrcode_id",
autoIncrement: false,
references: {
model: "qrcode",
key: "id"
}
},
publicityInfoId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: "关联宣传信息ID",
primaryKey: false,
field: "publicity_info_id",
autoIncrement: false,
references: {
model: "publicity_info",
key: "id"
}
},
}, {
tableName: "qrcode_files",
comment: "",
indexes: []
});
dc.models.QrcodeFiles = QrcodeFiles;
return QrcodeFiles;
};

34
api/app/lib/models/qrcode_labels.js

@ -0,0 +1,34 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const QrcodeLabels = sequelize.define("qrcodeLabels", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: "标签名",
primaryKey: false,
field: "name",
autoIncrement: false,
},
}, {
tableName: "qrcode_labels",
comment: "",
indexes: []
});
dc.models.QrcodeLabels = QrcodeLabels;
return QrcodeLabels;
};

64
api/app/lib/models/qrcode_labels_qrcode.js

@ -0,0 +1,64 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const QrcodeLabelsQrcode = sequelize.define("qrcode_labels_qrcode", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
},
labelId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "label_id",
autoIncrement: false,
references: {
model: "qrcode_labels",
key: "id"
}
},
qrcodeId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "qrcode_id",
autoIncrement: false,
references: {
model: "qrcode",
key: "id"
}
},
publicityInfoId: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "publicity_info_id",
autoIncrement: false,
references: {
model: "publicity_info",
key: "id"
}
},
}, {
tableName: "qrcode_labels_qrcode",
comment: "",
indexes: []
});
dc.models.QrcodeLabelsQrcode = QrcodeLabelsQrcode;
return QrcodeLabelsQrcode;
};

15
api/app/lib/routes/label/index.js

@ -0,0 +1,15 @@
'use strict';
const label = require('../../controllers/label/index');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/label'] = { content: '获取标签', visible: false };
router.get('/label', label.getLabels);
app.fs.api.logAttr['POST/label/add'] = { content: '新增标签', visible: true };
router.post('/label/add', label.createLabels);
app.fs.api.logAttr['DELETE/label/:id'] = { content: '删除标签', visible: true };
router.del('/label/:id', label.delLabels);
};

18
api/app/lib/routes/publicityInfo/index.js

@ -0,0 +1,18 @@
'use strict';
const publicityInfo = require('../../controllers/publicityInfo/index');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/publicityInfo'] = { content: '获取宣传信息', visible: false };
router.get('/publicityInfo', publicityInfo.getPublicityInfo);
app.fs.api.logAttr['POST/publicityInfo/add'] = { content: '新增宣传信息', visible: true };
router.post('/publicityInfo/add', publicityInfo.createPublicityInfo);
app.fs.api.logAttr['PUT/publicityInfo/:id/modify'] = { content: '修改宣传信息', visible: true };
router.put('/publicityInfo/:id/modify', publicityInfo.updatePublicityInfo);
app.fs.api.logAttr['DELETE/publicityInfo/:id/del'] = { content: '删除宣传信息', visible: true };
router.del('/publicityInfo/:id/del', publicityInfo.delPublicityInfo);
};

13
api/app/lib/routes/qrcode/index.js

@ -0,0 +1,13 @@
'use strict';
const qrcode = require('../../controllers/qrcode/index');
module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/qrcode'] = { content: '获取二维码', visible: false };
router.get('/qrcode', qrcode.getQrcode);
app.fs.api.logAttr['POST/qrcode/add'] = { content: '新增二维码', visible: true };
router.post('/qrcode/add', qrcode.createQrcode);
};

47
script/1.0.0/schema/2.init_qrcode.sql

@ -4,11 +4,15 @@ CREATE TABLE "public"."qrcode" (
"url" varchar(255) NOT NULL,
"name" varchar(128) NOT NULL,
"type" varchar(32) NOT NULL,
"key" uuid NOT NULL,
"logo" varchar(255),
PRIMARY KEY ("id")
);
COMMENT ON COLUMN "public"."qrcode"."name" IS '二维码名称';
COMMENT ON COLUMN "public"."qrcode"."type" IS '类型(文件|链接)';
COMMENT ON COLUMN "public"."qrcode"."url" IS '二维码图片存储链接';
COMMENT ON COLUMN "public"."qrcode"."name" IS '二维码名称';
COMMENT ON COLUMN "public"."qrcode"."type" IS '类型(图片|文件|链接|视频)';
COMMENT ON COLUMN "public"."qrcode"."key" IS '二维码唯一标识';
COMMENT ON COLUMN "public"."qrcode"."logo" IS '二维码中间的照片';
DROP TABLE IF EXISTS "public"."publicity_info";
@ -17,27 +21,56 @@ CREATE TABLE "public"."publicity_info" (
"name" varchar(128) NOT NULL,
"type" varchar(32) NOT NULL,
"time" timestamptz NOT NULL,
"qrcode_id" int4 NOT NULL,
"link" varchar(255),
"qrcode_id" int4,
PRIMARY KEY ("id"),
CONSTRAINT "publicity_info_qrcode_id_fk" FOREIGN KEY ("qrcode_id") REFERENCES "public"."qrcode" ("id")
);
COMMENT ON COLUMN "public"."publicity_info"."name" IS '宣传标题';
COMMENT ON COLUMN "public"."publicity_info"."type" IS '类型';
COMMENT ON COLUMN "public"."publicity_info"."type" IS '类型(图片|文件|链接|视频)';
COMMENT ON COLUMN "public"."publicity_info"."time" IS '创建/更新时间';
COMMENT ON COLUMN "public"."publicity_info"."link" IS 'type为链接时的链接地址';
COMMENT ON COLUMN "public"."publicity_info"."qrcode_id" IS '关联二维码ID';
DROP TABLE IF EXISTS "public"."qrcode_files";
CREATE TABLE "public"."qrcode_files" (
"id" serial,
"qrcode_id" int4 NOT NULL,
"file_name" varchar(128) NOT NULL,
"file_size" int4 NOT NULL,
"file_url" varchar(255) NOT NULL,
"preview_img_url" varchar(255),
"qrcode_id" int4,
"publicity_info_id" int4,
PRIMARY KEY ("id"),
CONSTRAINT "qrcode_files_qrcode_id_fk" FOREIGN KEY ("qrcode_id") REFERENCES "public"."qrcode" ("id")
CONSTRAINT "qrcode_files_qrcode_id_fk" FOREIGN KEY ("qrcode_id") REFERENCES "public"."qrcode" ("id"),
CONSTRAINT "qrcode_files_publicity_info_id_fk" FOREIGN KEY ("publicity_info_id") REFERENCES "public"."publicity_info" ("id")
);
COMMENT ON COLUMN "public"."qrcode_files"."qrcode_id" IS '关联二维码ID';
COMMENT ON COLUMN "public"."qrcode_files"."file_name" IS '文件名';
COMMENT ON COLUMN "public"."qrcode_files"."file_size" IS '文件大小(byte)';
COMMENT ON COLUMN "public"."qrcode_files"."file_url" IS '文件存储链接';
COMMENT ON COLUMN "public"."qrcode_files"."preview_img_url" IS '文件预览图链接';
COMMENT ON COLUMN "public"."qrcode_files"."qrcode_id" IS '关联二维码ID';
COMMENT ON COLUMN "public"."qrcode_files"."publicity_info_id" IS '关联宣传信息ID';
DROP TABLE IF EXISTS "public"."qrcode_labels";
CREATE TABLE "public"."qrcode_labels" (
"id" serial,
"name" varchar(128) NOT NULL,
PRIMARY KEY ("id")
);
COMMENT ON COLUMN "public"."qrcode_labels"."name" IS '标签名';
DROP TABLE IF EXISTS "public"."qrcode_labels_qrcode";
CREATE TABLE "public"."qrcode_labels_qrcode" (
"id" serial,
"label_id" int4 NOT NULL,
"qrcode_id" int4 NOT NULL,
"publicity_info_id" int4,
PRIMARY KEY ("id"),
CONSTRAINT "qrcode_labels_label_id_fk" FOREIGN KEY ("label_id") REFERENCES "public"."qrcode_labels" ("id"),
CONSTRAINT "qrcode_labels_qrcode_id_fk" FOREIGN KEY ("qrcode_id") REFERENCES "public"."qrcode" ("id"),
CONSTRAINT "qrcode_labels_publicity_info_id_fk" FOREIGN KEY ("publicity_info_id") REFERENCES "public"."publicity_info" ("id")
);

1
web/client/assets/images/document.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1706165283537" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1952" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M746.602 0.118H106.75v1023.764h810.5V170.734z" fill="#E6E9ED" p-id="1953"></path><path d="M234.72 341.368l97.508-162.51 97.51 162.51z" fill="#4A89DC" p-id="1954"></path><path d="M315.982 341.368l130.002-227.51 130 227.51z" fill="#5D9CEC" p-id="1955"></path><path d="M767.942 597.292H256.058c-11.78 0-21.338-9.53-21.338-21.308 0-11.808 9.558-21.338 21.338-21.338H767.94c11.778 0 21.338 9.53 21.338 21.338 0.002 11.78-9.56 21.308-21.336 21.308zM767.942 725.264H256.058c-11.78 0-21.338-9.53-21.338-21.306 0-11.81 9.558-21.34 21.338-21.34H767.94a21.328 21.328 0 0 1 21.338 21.34c0.002 11.776-9.56 21.306-21.336 21.306zM533.338 853.234h-277.28c-11.78 0-21.338-9.53-21.338-21.308 0-11.808 9.558-21.338 21.338-21.338h277.28c11.78 0 21.306 9.53 21.306 21.338 0 11.78-9.526 21.308-21.306 21.308z" fill="#434A54" p-id="1956"></path><path d="M746.602 0.118v170.616h170.648z" fill="#CCD1D9" p-id="1957"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
web/client/assets/images/logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 22 KiB

9
web/client/assets/images/logo.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.0 MiB

BIN
web/client/assets/images/logo_white.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

5
web/client/index.ejs

@ -3,13 +3,14 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<link rel="shortcut icon" href="/assets/images/logo.png">
<link rel="stylesheet" type="text/css" href="/assets/font_sc/iconfont.css">
<script type="text/javascript">
<!-- <script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode: 'e955cd5ddfc3a752aa27d1e1c67d182d',
}
</script>
</script> -->
<!-- <script src="https://webapi.amap.com/maps?v=2.0&key=00f9a29dedcdbd8befec3dfe0cef5003&plugin=AMap.AutoComplete,AMap.PlaceSearch"></script> -->
<!-- <script src="https://webapi.amap.com/loca?v=2.0.0&key=00f9a29dedcdbd8befec3dfe0cef5003"></script> -->
</head>

7
web/client/index.html

@ -3,14 +3,15 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<title></title>
<link rel="shortcut icon" href="/assets/images/logo.svg">
<link rel="shortcut icon" href="/assets/images/logo.png">
<link rel="stylesheet" type="text/css" href="/assets/font_sc/iconfont.css">
<script type="text/javascript">
<!-- <script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode: 'e955cd5ddfc3a752aa27d1e1c67d182d',
}
</script>
</script> -->
<!-- <script src="https://webapi.amap.com/maps?v=2.0&key=00f9a29dedcdbd8befec3dfe0cef5003&plugin=AMap.AutoComplete,AMap.PlaceSearch"></script> -->
<!-- <script src="https://webapi.amap.com/loca?v=2.0.0&key=00f9a29dedcdbd8befec3dfe0cef5003"></script> -->
</head>

52
web/client/src/components/QrcodeFilesUpload/fileInfoForm.js

@ -0,0 +1,52 @@
import React, { useRef } from 'react'
import { Spin, Upload, message, Modal, Card, Button, Input, Form } from 'antd'
import Uploads from '../Uploads'
export default function FileInfoForm({ }) {
const [form] = Form.useForm();
const handleOk = () => {
form.validateFields()
.then((values) => {
console.log(values, 'valuesvalues')
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}
return (
<Form
form={form}
layout="inline"
name="preview_form"
initialValues={{}}
>
<Form.Item
name="fileName"
label="文件名"
rules={[{ required: true, message: '请输入文件名' }]}
>
<Input style={{ width: '200px' }} />
</Form.Item>
<Form.Item
name="previewImg"
label="预览图上传"
rules={[]}
>
<Uploads
listType='picture-card'
uploadType='image'
maxFilesNum={1}
maxFileSize={5}
isQiniu={true}
fileTypes={["png", "jpg"]}
defaultValue={([]).map(s => {
return {
storageUrl: s
}
})}
/>
</Form.Item>
</Form>
)
}

413
web/client/src/components/QrcodeFilesUpload/index.js

@ -0,0 +1,413 @@
'use strict';
import React, { Component, useRef } from 'react';
import { connect } from 'react-redux';
import { Spin, Upload, message, Modal, Card, Button, Input, Form } from 'antd';
import Uploads from '../Uploads';
import moment from 'moment';
import { PlusOutlined, UploadOutlined, CloseOutlined } from '@ant-design/icons';
import './index.less'
class QrcodeFilesUpload extends Component {
constructor(props) {
super(props);
this.ApiRoot = localStorage.getItem('tyApiRoot')
this.qnDomain = localStorage.getItem('qnDomain');
this.aliAdmin = localStorage.getItem('aliAdmin');
this.state = {
fileUploading: false,
fileList: [],
curPreviewPic: '',
curPreviewVideo: '',
delPicIng: false,
removeFilesList: []
};
}
dealName = (uploaded) => {
let realName = uploaded?.split('/')[2]
return realName
}
setFileList = (nextEditData, isQiniu, isAli) => {
let defaultFileList = [];
defaultFileList = nextEditData.map((u, index) => {
let fileUrl =
isQiniu ? `/_file-server/${u.storageUrl}`
: isAli ? `/_file-ali-server/${u.storageUrl}`
: `${this.ApiRoot}/${u.storageUrl}`;
return {
uid: -index - 1,
name: this.dealName(u.storageUrl),
status: 'done',
storageUrl: u.storageUrl,
url: fileUrl,
size: u.size || -1,
customFileName: u.customFileName,
previewImgUrl: u.previewImgUrl,
};
});
this.setState({
fileList: defaultFileList
});
};
componentDidMount() {
const { value, defaultValue, isQiniu, isAli } = this.props;
if (defaultValue) {
this.setFileList(defaultValue, isQiniu, isAli)
}
}
UNSAFE_componentWillReceiveProps(np) {
const { dispatch, value: thisEditData, onChange } = this.props;
const { value: nextEditData, isQiniu, isAli } = np;
if (nextEditData && nextEditData.length) {
if (!thisEditData || !this.state.fileList.length) {
this.setFileList(nextEditData, isQiniu, isAli);
} else if (nextEditData.length != thisEditData.length) {
this.setFileList(nextEditData, isQiniu, isAli);
} else {
let repeat = true;
for (let i = 0; i < thisEditData.length; i++) {
if (thisEditData[i] != nextEditData[i]) {
repeat = false;
break;
}
}
if (!repeat) {
this.setFileList(nextEditData, isQiniu, isAli);
}
}
}
}
render() {
const UploadPath = {
project: ['txt', 'dwg', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'pdf', 'pptx', 'png', 'jpg', 'svg', 'jpeg', 'rar', 'zip', 'jpeg', 'mp4'],
report: ['doc', 'docx', 'xls', 'xlsx', 'csv', 'pdf'],
data: ['txt', 'xls', 'xlsx', 'csv'],
image: ['png', 'jpg', 'svg', 'jpeg'],
three: ['js'],
video: ['mp4']
};
/**
* uploadType string 主要区别文件上传路径 以及类型 web/routes/attachment/index.js UploadPath key 值为准默认 project
* disabled boolean 上传是否可用
* maxFilesNum number 最大上传数量
* fileTypes array[string] 可允许上传的文件类型
* maxFileSize number 单个文件最大大小 M
* listType antd upload 组件的属性
* onChange function 文件数量变化时候回调 返回文件
* value array[obj] 编辑数据 [{url:'xxx', [size:999]}]
* onStateChange function 文件状态改变回调函数 上传中 return { uploading:true/false }
*/
const {
uploadType,
disabled,
maxFilesNum,
fileTypes,
maxFileSize,
listType,
onChange = () => { },
value,
showUploadList,
onStateChange,
isQiniu,
isAli,
} = this.props;
const { fileList, curPreviewPic, curPreviewVideo, delPicIng, removeFilesList } = this.state;
const that = this;
let uploadType_ = uploadType || 'project';
let maxFilesNum_ = maxFilesNum || 1;
let defaultFileTypes = fileTypes || UploadPath[uploadType_];
// debugger
const uploadProps = {
name: 'checkFile_',
multiple: false,
showUploadList: showUploadList || true,
action:
isQiniu ? `/_upload/attachments/${uploadType_}`
: isAli ? `/_upload/attachments/ali/${uploadType_}`
: `${this.ApiRoot}/attachments/${uploadType_}`,
listType: listType || 'text',
disabled: disabled,
beforeUpload: (file) => {
if (fileList.length >= maxFilesNum_) {
message.warning(`最多选择${maxFilesNum_}个文件上传`);
return false;
}
if (file.name.length > 60) {
message.warning(`文件名过长(大于60字符),请修改后上传`);
return false;
}
const extNames = file.name?.split('.');
// var reg = /^[\.\s\u4e00-\u9fa5a-zA-Z0-9_-]{0,}$/;
// if (!reg.exec(file.name)) {
// message.warning(`文件名包含除字母、汉字、数字、中划线、下划线之外的字符,请修改后上传`);
// return false;
// }
let isDAE = false;
if (extNames.length > 0) {
let fileType = extNames[extNames.length - 1].toLowerCase();
isDAE = defaultFileTypes.some((f) => f == fileType);
}
if (!isDAE) {
message.error(`只能上传 ${defaultFileTypes.join()} 格式的文件!`);
return false;
}
const isLt = file.size / 1024 / 1024 < (maxFileSize || 3);
if (!isLt) {
message.error(`文件必须小于${maxFileSize || 3}MB!`);
return false;
}
this.setState({
fileUploading: true
});
if (onStateChange) {
onStateChange({ uploading: true });
}
},
onChange(info) {
const status = info.file.status;
if (status === 'uploading') {
that.setState({
fileList: info.fileList
});
}
if (status === 'done') {
let { uploaded, url } = info.file.response;
let size = info.file.size;
let nextFileList = fileList;
nextFileList[nextFileList.length - 1] = {
uid: -moment().unix(),
name: that.dealName(uploaded),
status: 'done',
storageUrl: uploaded,
url:
isQiniu ? '/_file-server/' + uploaded :
isAli ? `/_file-ali-server/${uploaded}` :
url,
size: size
};
onChange(nextFileList);
that.setState({
fileUploading: false,
fileList: nextFileList
});
if (onStateChange) {
onStateChange({ uploading: false });
}
} else if (status === 'error') {
that.setState({
fileUploading: false
});
message.error(`${info.file.name} 上传失败,请重试`);
if (onStateChange) {
onStateChange({ uploading: false });
}
}
},
onRemove(file) {
let nextFileList = [];
fileList.map((f, i) => {
if (f.uid != file.uid) {
nextFileList.push(f);
}
});
let nextRemoveFiles = removeFilesList.concat([file.storageUrl]);
if (curPreviewPic == file.url) {
that.setState({
curPreviewPic: ''
});
}
if (curPreviewVideo == file.url) {
that.setState({
curPreviewVideo: ''
});
}
onChange(nextFileList);
that.setState({
fileList: nextFileList,
removeFilesList: nextRemoveFiles
});
},
onPreview(file) {
let filePostfix = file.url?.split('.').pop();
filePostfix = filePostfix.toLowerCase();
if (UploadPath.image.some((img) => img == filePostfix)) {
that.setState({
curPreviewPic: file.url
});
} else if (UploadPath.video.some((img) => img == filePostfix)) {
that.setState({
curPreviewVideo: file.url
});
} else {
//message.warn('仅支持图片预览');
preview(file.storageUrl)
}
}
};
const preview = (url) => {
let link = isQiniu ? encodeURI(`${this.qnDomain}/${url}`) :
isAli ? encodeURI(`${this.aliAdmin}/${url}`) : ''
if (link)
if (url.indexOf("pdf") !== -1 || url.indexOf("csv") !== -1) {
window.open(link)
} else {
window.open(`https://view.officeapps.live.com/op/view.aspx?src=${link}`)
}
}
let fileList_ = fileList
//下载文件
const handleDownload = (file) => {
saveAs(file)
};
const saveAs = (file) => {
const link = document.createElement('a');
link.href = file.url;
link.download = file.name;
link.style.display = 'none';
link.click();
}
//自定义下载
return (
<div>
<Spin spinning={delPicIng}>
<Upload
{...uploadProps}
fileList={fileList_}
showUploadList={{ showDownloadIcon: true }}
onDownload={handleDownload}
itemRender={(originNode, file, currFileList) => {
const fileName = file.name?.split('.').slice(0, -1).join('.')
const extension = file.name?.split('.').pop()
return <>
{originNode}
<Form
layout="inline"
name="preview_form"
initialValues={{
fileName: fileName
}}
>
<Form.Item
name="fileName"
label="文件标题"
rules={[]}
>
<Input
onChange={({ target: { value } }) => {
const fileInfo = {
...file,
customFileName: value,
};
const nextFileList = currFileList.toSpliced(
currFileList.findIndex(item => item.storageUrl === file.storageUrl),
1,
fileInfo,
);
onChange(nextFileList);
this.setState({ fileList: nextFileList })
}}
placeholder="不填写默认为上传的文件名"
style={{ width: '220px' }}
/>
</Form.Item>
{!UploadPath.image.includes(extension) && <Form.Item
name="previewImg"
label="预览图上传"
rules={[]}
>
<Uploads
listType='picture-card'
uploadType='image'
maxFilesNum={1}
maxFileSize={5}
isQiniu={true}
fileTypes={["png", "jpg", "jpeg"]}
onChange={(previewFile) => {
const fileInfo = {
...file,
previewImgUrl: previewFile[0]?.storageUrl,
};
const nextFileList = currFileList.toSpliced(
currFileList.findIndex(item => item.storageUrl === file.storageUrl),
1,
fileInfo,
);
onChange(nextFileList);
this.setState({ fileList: nextFileList })
}}
defaultValue={([]).map(s => {
return {
storageUrl: s
}
})}
/>
</Form.Item>}
</Form>
</>
}}
>
{
disabled ? ('') :
listType == 'picture-card' ?
(
fileList.length >= maxFilesNum_ ? null : (
<div style={{}}>
<PlusOutlined />
<div>添加附件</div>
</div>
)
) : (
<Button disabled={fileList.length >= maxFilesNum_} icon={<UploadOutlined />}> 文件上传 </Button>
)
}
</Upload>
{
curPreviewPic ? (
<Card bodyStyle={{ padding: 8 }}>
<div style={{ marginBottom: 8 }} >
<span>图片预览</span>
<span style={{ float: 'right' }} onClick={() => { this.setState({ curPreviewPic: '' }) }}>
<CloseOutlined style={{ fontSize: 20 }} />
</span>
</div>
<img style={{ width: '100%' }} src={curPreviewPic} />
</Card>
) : ''
}
{
curPreviewVideo ? (<Card bodyStyle={{ padding: 8 }}>
<div style={{ marginBottom: 8 }} >
<span>视频预览</span>
<span style={{ float: 'right' }} onClick={() => { this.setState({ curPreviewVideo: '' }) }}>
<CloseOutlined style={{ fontSize: 20 }} />
</span>
</div>
<video controls style={{ width: '100%' }}>
<source src={curPreviewVideo} type="video/mp4"></source>
</video>
</Card>) : ''
}
</Spin>
</div>
);
}
}
function mapStateToProps(state) {
const { auth } = state
return {
user: auth.user
};
}
export default connect(mapStateToProps)(QrcodeFilesUpload);

0
web/client/src/components/QrcodeFilesUpload/index.less

2
web/client/src/layout/components/header/index.js

@ -29,7 +29,7 @@ const Header = props => {
return (
<div className={styles.header}>
<div className={styles['header-fold']}>
<img src='/assets/images/logo.svg' style={{ margin: '-14px 12px 0 0', height: 24 }} />
<img src='/assets/images/logo_white.png' style={{ margin: '-14px 12px 0 0', height: 36 }} />
<div className={styles['header-title']} style={{}}>
<div className={styles['title-cn']}>飞尚码尚来二维码管理系统</div>
<div className={styles['title-en']}>FREESUN QRCODE MANAGEMENT SYSTEM</div>

22
web/client/src/sections/organization/actions/authority.js

@ -3,16 +3,16 @@
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getAuthority(orgId) {
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_MEMBERS',
url: `${ApiTable.getEnterprisesMembers.replace('{enterpriseId}', orgId)}`,
msg: { error: '获取用户列表失败' },
reducer: { name: 'members' }
});
}
// export function getAuthority(orgId) {
// return dispatch => basicAction({
// type: 'get',
// dispatch: dispatch,
// actionType: 'GET_MEMBERS',
// url: `${ApiTable.getEnterprisesMembers.replace('{enterpriseId}', orgId)}`,
// msg: { error: '获取用户列表失败' },
// reducer: { name: 'members' }
// });
// }
export function getResource(userId) {
return dispatch => basicAction({
type: 'get',
@ -44,7 +44,7 @@ export function postUserRes(body) {
});
}
export default {
getAuthority,
// getAuthority,
getResource,
getUserResource,
postUserRes

2
web/client/src/sections/publicityInfoConfig/actions/index.js

@ -2,7 +2,9 @@
import * as publicityInfoConfig from './publicityInfoConfig'
import * as label from './label'
export default {
...publicityInfoConfig,
...label,
}

38
web/client/src/sections/publicityInfoConfig/actions/label.js

@ -0,0 +1,38 @@
'use strict';
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getLabels(query) {
return (dispatch) => basicAction({
type: 'get',
query,
dispatch,
actionType: 'GET_LABELS',
url: ApiTable.getLabels,
msg: { error: '获取标签失败', },
reducer: { name: 'labels' }
});
}
export function createLabels(data) {
return (dispatch) => basicAction({
type: 'post',
data,
dispatch,
actionType: 'POST_LABELS',
url: ApiTable.createLabels,
msg: { option: '新增标签', },
});
}
export function delLabels(id) {
return (dispatch) => basicAction({
type: 'del',
dispatch,
actionType: 'DEL_LABELS',
url: ApiTable.delLabels.replace('{id}', id),
msg: { option: '删除标签', },
});
}

79
web/client/src/sections/publicityInfoConfig/actions/publicityInfoConfig.js

@ -3,82 +3,50 @@
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getProjectList (query) {
export function getPublicityInfo(query) {
return (dispatch) => basicAction({
type: 'get',
query,
dispatch,
actionType: 'GET_PROJEECT_LIST',
url: ApiTable.getProjectList,
msg: { error: '获取结构物列表失败', },
actionType: 'GET_PUBLICITY_INFO',
url: ApiTable.getPublicityInfo,
msg: { error: '获取宣传信息失败', },
});
}
export function postAddProject (data) {
export function createPublicityInfo(data) {
return (dispatch) => basicAction({
type: 'post',
data,
dispatch,
actionType: 'POST_ADD_PROJECT',
url: ApiTable.postAddProject,
msg: { option: data?.id ? '编辑结构物' : '新增结构物', },
});
}
export function delProject (id) {
return (dispatch) => basicAction({
type: 'del',
dispatch,
actionType: 'DEL_PROJECT',
url: ApiTable.delProject.replace('{id}', id),
msg: {
option: '删除结构物',
},
actionType: 'POST_PUBLICITY_INFO',
url: ApiTable.createPublicityInfo,
msg: { option: '新增宣传信息', },
});
}
export function addPosition (data, point) {
export function updatePublicityInfo(data, id) {
return (dispatch) => basicAction({
type: 'post',
type: 'put',
data,
dispatch,
actionType: 'ADD_POSITION',
url: ApiTable.position,
msg: { option: point ? '二维码生成' : data?.id ? '编辑点位' : '新增点位', },
});
}
export function positionList (query) {
return (dispatch) => basicAction({
type: 'get',
query,
dispatch,
actionType: 'POSITION_LIST',
url: ApiTable.position,
msg: { error: '获取点位列表失败', },
reducer: { name: 'projectPoints' }
actionType: 'PUT_PUBLICITY_INFO',
url: ApiTable.updatePublicityInfo.replace('{id}', id),
msg: { option: '修改宣传信息', },
});
}
export function delPosition (id) {
export function delPublicityInfo(id, qrcodeId) {
return (dispatch) => basicAction({
type: 'del',
dispatch,
actionType: 'DEL_POSITION',
url: ApiTable.delPosition.replace('{id}', id),
msg: {
option: '删除点位',
},
actionType: 'DEL_PUBLICITY_INFO',
url: ApiTable.delPublicityInfo.replace('{id}', id).replace('{qrcodeId}', qrcodeId),
msg: { option: '删除宣传信息', },
});
}
export function qrCodeShow (query) {
export function qrCodeShow(query) {
return (dispatch) => basicAction({
type: 'get',
query,
@ -88,14 +56,3 @@ export function qrCodeShow (query) {
msg: { error: '获取二维码列表失败', },
});
}
export function q () {
return (dispatch) => basicAction({
type: 'get',
dispatch,
actionType: 'GET_CODE',
url: ApiTable.q,
msg: { error: '获取二维码列表失败', },
});
}

273
web/client/src/sections/publicityInfoConfig/components/publicityInfoModal.js

@ -0,0 +1,273 @@
import React, { useState, useEffect } from 'react';
import { Button, Form, Input, Modal, Select, Divider, Row, Col, Image, message } from 'antd';
import QrcodeFilesUpload from '../../../components/QrcodeFilesUpload';
import { Uploads } from '../../../components';
import { connect } from 'react-redux';
import { getLabels } from '../actions/label';
import { createPublicityInfo, updatePublicityInfo } from '../actions/publicityInfoConfig';
import QrCodeWithLogo from 'qr-code-with-logo'
import { v4 as uuidv4 } from 'uuid';
import request from 'superagent';
const PublicityInfoModal = ({ visible, onCancel, updateList, dispatch, curRecord, labels }) => {
const [form] = Form.useForm();
const [type, setType] = useState('图片');
const [qrcode, setQrcode] = useState('');
useEffect(() => {
getLabelsData()
if (curRecord) {
console.log(curRecord, 'curRecord')
setType(curRecord.type)
} else {
draw()
}
}, [])
const getLabelsData = () => {
dispatch(getLabels({}));
}
const draw = () => {
const ctx = document.getElementById("qrcodeCanvas").getContext("2d");
ctx.fillStyle = "#f5f5f5";
ctx.fillRect(0, 0, 220, 220);
ctx.fillStyle = "black";
ctx.font = "14px serif";
ctx.fillText("此处预览二维码", 60, 110);
}
const handleSubmit = async (values) => {
console.log(values, 'values')
if (curRecord) {
// dispatch(updatePublicityInfo(params)).then(res => {
// if (res.success) {
// // form.resetFields();
// // onCancel();
// }
// })
} else {
const key = uuidv4();
// 生成二维码
let qrcodeUrl = '';
const qrcodeCanvas = document.getElementById('qrcodeCanvas')
let options = {
canvas: qrcodeCanvas,
width: 220,
content: values.type === '链接'
? values.link
: `${window.location.protocol}//${window.location.host}/scan?key=${key}`,
}
if (values.qrcodeImg?.length) {
options.logo = {
src: '/_file-server/' + values.qrcodeImg[0].storageUrl,
radius: 8
}
}
await QrCodeWithLogo.toCanvas(options)
// 上传二维码
try {
const blob = await canvasToBlob(qrcodeCanvas);
const formData = new FormData();
formData.append('image', blob, values.name + '.png');
const res = await request.post('/_upload/attachments/image', formData)
qrcodeUrl = res.body.uploaded
} catch (error) {
console.log(error);
message.error('二维码上传失败');
return;
}
console.log(qrcodeUrl, 'qrcodeUrl')
dispatch(createPublicityInfo({
...values,
qrcode: { url: qrcodeUrl, key },
})).then(res => {
if (res.success) {
form.resetFields();
onCancel();
updateList();
}
})
}
}
const handleChange = (value) => {
console.log(`selected ${value}`);
};
return (
<Modal
width={700}
visible={visible}
title={`${curRecord ? "编辑" : "新增"}宣传项`}
okText="确定"
cancelText="取消"
footer={[
<Button key="submit" type="primary" onClick={() => {
form.validateFields()
.then((values) => {
handleSubmit(values)
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}>
生成二维码并保存
</Button>,
// <Button type="primary" onClick={() => { }}>
// 生成二维码
// </Button>,
<Button key="back" onClick={() => {
form.resetFields();
onCancel();
}}>取消</Button>,
]}
onCancel={() => {
form.resetFields();
onCancel();
}}
>
<Form
form={form}
layout="vertical"
name="form_in_modal"
initialValues={{
...curRecord,
type: curRecord?.type || type,
}}
onValuesChange={() => {
}}
>
<Form.Item
name="name"
label="宣传标题"
rules={[{ required: true, message: '请输入宣传标题' }]}
>
<Input />
</Form.Item>
<Form.Item
name="type"
label="类型"
rules={[{ required: true, message: '请选择类型' }]}
>
<Select
value={type}
options={[
{
value: '图片',
label: '图片',
},
{
value: '文件',
label: '文件',
},
{
value: '链接',
label: '链接',
},
{
value: '视频',
label: '视频',
},
]}
style={{ width: '50%' }}
onChange={(value) => { setType(value); }}
/>
</Form.Item>
{type === '链接' ? <Form.Item
name="link"
label="链接地址"
rules={[{ required: true, message: '请填写链接地址' }]}
>
<Input placeholder='http://' />
</Form.Item> : <Form.Item
name="files"
label={`${type}上传`}
rules={[{ required: true, message: '请上传文件' }]}
>
<QrcodeFilesUpload
listType='list'
maxFilesNum={50}
maxFileSize={500}
isQiniu={true}
uploadType={type === '图片' ? 'image' : type === '视频' ? 'video' : 'project'}
// fileTypes={["png", "jpg", "mp4"]}
defaultValue={([]).map(s => {
return {
storageUrl: s
}
})}
/>
</Form.Item>}
<Divider />
<Row>
<Col span={14}>
<Form.Item
name="qrcodeImg"
label="二维码照片"
rules={[]}
>
<Uploads
listType='picture-card'
uploadType='image'
maxFilesNum={1}
maxFileSize={5}
isQiniu={true}
fileTypes={["png", "jpg", "jpeg"]}
defaultValue={([]).map(s => {
return {
storageUrl: s
}
})}
/>
</Form.Item>
<Form.Item
name="labels"
label="标签"
rules={[]}
>
<Select
mode="tags"
style={{ width: '80%' }}
placeholder="输入或选择"
onChange={handleChange}
options={labels}
fieldNames={{ label: 'name', value: 'id' }}
/>
</Form.Item>
</Col>
<Col span={10}>
{qrcode
? <Image width={220} src={qrcode} />
: <canvas id="qrcodeCanvas" width="220" height="220" />
}
</Col>
</Row>
</Form>
</Modal>
);
};
function mapStateToProps(state) {
const { auth, labels } = state
return {
user: auth.user,
labels: labels.data || [],
}
}
export default connect(mapStateToProps)(PublicityInfoModal);
function canvasToBlob(canvas) {
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (blob) {
resolve(blob);
} else {
reject(new Error('无法生成Blob对象'));
}
});
});
}

145
web/client/src/sections/publicityInfoConfig/containers/publicityInfoConfig.js

@ -1,16 +1,147 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { connect } from 'react-redux';
import { Form, Input, Select, Button, message } from 'antd';
import { Form, Input, Select, Button, message, Popconfirm } from 'antd';
import ProTable from '@ant-design/pro-table';
import PublicityInfoModal from '../components/publicityInfoModal';
import moment from 'moment';
import './publicityInfoConfig.less';
const PublicityInfoConfig = (props) => {
const { dispatch, actions } = props
const { getPublicityInfo, delPublicityInfo } = actions.publicityInfoConfig
const ref = useRef()
return (
<div className='global-main'>
宣传信息配置
</div>
)
const [dataSource, setDataSource] = useState([]);
const [visible, setVisible] = useState(false);
const [curRecord, setCurRecord] = useState();
const columns = [
{
title: '宣传标题',
dataIndex: 'name',
key: 'name',
ellipsis: true,
},
{
title: '标签',
dataIndex: 'label',
key: 'label',
ellipsis: true,
search: false,
render: (_, record) => {
return <div>{record.qrcodeLabels?.map(l => l.name)?.join() || '-'}</div>
}
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
width: 100,
ellipsis: true,
search: false,
},
{
title: '附件',
dataIndex: 'files',
key: 'files',
search: false,
ellipsis: true,
render: (_, record) => {
return <div>{
record.type === '链接'
? <a href={record.link} target="_blank">{record.link}</a>
: record.qrcodeFiles?.map(f => <div>
<a href={'/_file-server/' + f.fileUrl}>{f.fileName}</a>
</div>)
}</div>
}
},
{
title: '时间',
dataIndex: 'time',
key: 'time',
search: false,
ellipsis: true,
width: 150,
render: (_, record) => {
return <div>{moment(record.time).format('YYYY-MM-DD HH:mm:ss')}</div>
}
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
search: false,
ellipsis: true,
width: 150,
render: (_, record) => {
return <>
{/* <Button type="link" onClick={() => {
setCurRecord(record);
setVisible(true);
}}>编辑</Button> */}
<Popconfirm
title="确定删除吗?"
onConfirm={() => {
dispatch(delPublicityInfo(record.id, record.qrcodeId)).then(() => {
message.success('删除成功');
ref.current.reloadAndRest();
})
}}
onCancel={() => { }}
okText="确定"
cancelText="取消"
>
<Button danger type="link">删除</Button>
</Popconfirm >
</>
}
},
];
return (<>
<ProTable
actionRef={ref}
columns={columns}
dataSource={dataSource || []}
pagination={{ pageSize: 10, size: 'default' }}
options={false}
rowKey="id"
request={async (params = {}) => {
const res = await dispatch(getPublicityInfo({
limit: params?.pageSize,
page: params?.current - 1,
name: params?.name,
}));
setDataSource(res?.payload.data?.rows);
return {
...res,
total: res.payload.data.count ? res.payload.data.count : 0,
};
}}
search={{
defaultCollapsed: false,
optionRender: (searchConfig, formProps, dom) => [
...dom.reverse(),
<Button
key="add"
type='primary'
onClick={() => { setVisible(true) }}
>新增</Button>,
],
}}
onReset={() => { }}
/>
{visible && <PublicityInfoModal
visible={visible}
curRecord={curRecord}
updateList={() => { ref.current.reloadAndRest() }}
onCancel={() => {
setVisible(false)
setCurRecord()
}}
/>}
</>)
}
function mapStateToProps(state) {

89
web/client/src/sections/qrCode/actions/qrCode.js

@ -3,98 +3,13 @@
import { basicAction } from '@peace/utils'
import { ApiTable } from '$utils'
export function getProjectList (query) {
return (dispatch) => basicAction({
type: 'get',
query,
dispatch,
actionType: 'GET_PROJEECT_LIST',
url: ApiTable.getProjectList,
msg: { error: '获取结构物列表失败', },
});
}
export function postAddProject (data) {
return (dispatch) => basicAction({
type: 'post',
data,
dispatch,
actionType: 'POST_ADD_PROJECT',
url: ApiTable.postAddProject,
msg: { option: data?.id ? '编辑结构物' : '新增结构物', },
});
}
export function delProject (id) {
return (dispatch) => basicAction({
type: 'del',
dispatch,
actionType: 'DEL_PROJECT',
url: ApiTable.delProject.replace('{id}', id),
msg: {
option: '删除结构物',
},
});
}
export function addPosition (data, point) {
return (dispatch) => basicAction({
type: 'post',
data,
dispatch,
actionType: 'ADD_POSITION',
url: ApiTable.position,
msg: { option: point ? '二维码生成' : data?.id ? '编辑点位' : '新增点位', },
});
}
export function positionList (query) {
return (dispatch) => basicAction({
type: 'get',
query,
dispatch,
actionType: 'POSITION_LIST',
url: ApiTable.position,
msg: { error: '获取点位列表失败', },
reducer: { name: 'projectPoints' }
});
}
export function delPosition (id) {
return (dispatch) => basicAction({
type: 'del',
dispatch,
actionType: 'DEL_POSITION',
url: ApiTable.delPosition.replace('{id}', id),
msg: {
option: '删除点位',
},
});
}
export function qrCodeShow (query) {
export function getQrCode (query) {
return (dispatch) => basicAction({
type: 'get',
query,
dispatch,
actionType: 'GET_QR_CODE',
url: ApiTable.qrCodeShow,
msg: { error: '获取二维码列表失败', },
});
}
export function q () {
return (dispatch) => basicAction({
type: 'get',
dispatch,
actionType: 'GET_CODE',
url: ApiTable.q,
url: ApiTable.getQrCode,
msg: { error: '获取二维码列表失败', },
});
}

3
web/client/src/sections/qrCode/containers/index.js

@ -2,5 +2,6 @@
import QrCode from './qrCode'
import ScanCode from './scanCode'
export { QrCode, };
export { QrCode, ScanCode };

18
web/client/src/sections/qrCode/containers/qrCode.js

@ -17,7 +17,7 @@ const QrCode = (props) => {
const projectList = (obj) => {
const { projectId, name } = obj
dispatch(qrCode.qrCodeShow({ projectId, name })).then(res => {
dispatch(qrCode.getQrCode({ name })).then(res => {
if (res.success) {
setTableList(res.payload.data?.rows)
}
@ -39,7 +39,7 @@ const QrCode = (props) => {
let promiseArr = [], nameArr = []
tableList.forEach(l => {
promiseArr.push(
window.fetch(`/_file-server/${l.qrCode}`, {
window.fetch(`/_file-server/${l.url}`, {
method: 'get',
headers: {
"Accept": "application/json",
@ -60,7 +60,7 @@ const QrCode = (props) => {
}
zip.generateAsync({ type: "blob" }).then(blob => {
const url = window.URL.createObjectURL(blob)
downloadFile(url, "点位二维码.zip")
downloadFile(url, "二维码.zip")
setDownloading(false)
message.success('下载成功')
});
@ -87,11 +87,11 @@ const QrCode = (props) => {
}}
>
<Form.Item
label='点位名称'
label='名称'
name="name"
style={{ marginRight: 16, width: 260 }}
>
<Input placeholder="请输入点位名称" allowClear />
<Input placeholder="请输入名称" allowClear />
</Form.Item>
<Form.Item wrapperCol={{}}>
<Button htmlType="submit" style={{ marginRight: 16 }}>搜索</Button>
@ -111,22 +111,22 @@ const QrCode = (props) => {
borderRadius: '3px'
}}
>
<img src={`/_file-server/${v.qrCode}`} style={{ display: 'inline-block', width: 234 }} onError={imgError} />
<img src={`/_file-server/${v.url}`} style={{ display: 'inline-block', width: 234 }} onError={imgError} />
<div style={{
display: 'flex', flexDirection: 'column', padding: '6px 0',
width: 220, alignItems: 'center'
}}>
<span className='stru-name'>{firmList?.filter(u => u.value == v.projectId)[0]?.label}</span>
<span className='point-name'>点位名称{v.name}</span>
<span className='point-name'>名称{v.name}</span>
</div>
<div style={{
width: 234, height: 60, display: 'flex',
justifyContent: 'center', alignItems: 'center',
}}>
<Button type="primary" onClick={() => {
const tempArr = v.qrCode.split('/')
const tempArr = v.url.split('/')
const filename = tempArr[tempArr.length - 1]
downloadFile(`/_file-server/${v.qrCode}`, filename)
downloadFile(`/_file-server/${v.url}`, filename)
}}>下载二维码</Button>
</div>
</div>

141
web/client/src/sections/qrCode/containers/scanCode.js

@ -0,0 +1,141 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { SafeArea, AutoCenter, Button, Card, Toast, Image, Popup, Tag, Space, WaterMark, Divider } from 'antd-mobile';
import { getQrCode } from '../actions/qrCode';
import './scanCode.less';
const fileType = {
report: ['doc', 'docx', 'xls', 'xlsx', 'csv', 'pdf', 'pptx'],
image: ['png', 'jpg', 'svg', 'jpeg'],
video: ['mp4']
};
const ScanCode = ({ dispatch }) => {
const urlParams = new URLSearchParams(window.location.search);
const key = urlParams.get('key');
const qnDomain = localStorage.getItem('qnDomain');
const [qrCode, setQrCode] = useState({});
const [visible, setVisible] = useState(false)
const [link, setLink] = useState('')
useEffect(() => {
if (!key) {
Toast.show({
icon: 'fail',
content: '二维码参数错误',
})
return
}
getQrCodeData(key)
}, [])
const getQrCodeData = (key) => {
dispatch(getQrCode({ key })).then(res => {
console.log(res, 'res')
if (!res.payload?.data?.rows?.length) {
Toast.show({ icon: 'fail', content: '该二维码已被删除,无法访问' })
return
}
setQrCode(res.payload.data.rows[0])
})
}
const { qrcodeFiles } = qrCode;
return (<>
<SafeArea position='top' />
<AutoCenter>{qrCode.name}</AutoCenter>
<Divider />
{
qrcodeFiles?.map((f, i) => {
const extensionName = getFileExtension(f.fileUrl)
if (fileType.report.includes(extensionName)) {
return <Card key={i}
title={<Space>
<div className='text---' style={{ maxWidth: '60vw' }}>{f.fileName}</div>
<Tag round color='#2db7f5'>{extensionName}</Tag>
</Space>}
extra={<Button
size='small'
color='primary'
onClick={() => { window.open(`/_file-server/${f.fileUrl}`, '_self') }}
>下载</Button>}
>
<Image
src={f.previewImgUrl ? `/_file-server/${f.previewImgUrl}` : '/assets/images/document.svg'}
style={{ width: '100%', height: '200px' }}
fit='contain'
onClick={() => {
// const link = encodeURI(`https://view.officeapps.live.com/op/view.aspx?src=${qnDomain}/${f.fileUrl}`);
const nextLink = encodeURI(`https://view.xdocin.com/view?src=${qnDomain}/${f.fileUrl}`);
setLink(nextLink)
setVisible(true)
}}
/>
</Card>
}
else if (fileType.image.includes(extensionName)) {
return <Card key={i} title={<Space>
<div className='text---' style={{ maxWidth: '70vw' }}>{f.fileName}</div>
<Tag round color='#2db7f5'>{extensionName}</Tag>
</Space>}>
<Image
src={`/_file-server/${f.fileUrl}`}
style={{ width: '100%', height: '200px' }}
fit='contain'
/>
</Card >
}
else if (fileType.video.includes(extensionName)) {
return <Card key={i} title={<Space>
<div className='text---' style={{ maxWidth: '70vw' }}>{f.fileName}</div>
<Tag round color='#2db7f5'>{extensionName}</Tag>
</Space>}>
<video
style={{ width: '100%' }}
src={`/_file-server/${f.fileUrl}`}
poster={f.previewImgUrl ? `/_file-server/${f.previewImgUrl}` : undefined}
controls
/>
</Card>
}
})
}
<Popup
visible={visible}
onMaskClick={() => {
setVisible(false)
}}
position='bottom'
bodyStyle={{ height: '80vh' }}
closeOnMaskClick
>
<iframe
width="100%"
height="100%"
frameBorder='1'
src={link}>
</iframe>
<WaterMark content='FREESUM 飞尚科技' />
</Popup>
<SafeArea position='bottom' />
</>)
}
function mapStateToProps(state) {
const { global } = state;
return {
actions: global.actions,
};
}
export default connect(mapStateToProps)(ScanCode);
const getFileExtension = (fileUrl) => {
const fileNameWithExtension = fileUrl.split('/').pop(); // 获取URL的最后一部分(即文件名和扩展名)
const extension = fileNameWithExtension.split('.').pop(); // 从文件名中分离出扩展名
// 如果没有扩展名或文件名不含点,则返回空字符串
return extension ? extension.toLowerCase() : '';
};

16
web/client/src/sections/qrCode/containers/scanCode.less

@ -0,0 +1,16 @@
// @import '~antd-mobile/dist/antd-mobile.css';
html {
font-size: 24px;
}
body {
font-size: 1rem;
}
.text--- {
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
white-space: nowrap;
}

47
web/client/src/sections/qrCode/routes.js

@ -1,21 +1,32 @@
'use strict';
import { QrCode, } from './containers';
import { QrCode, ScanCode } from './containers';
export default [{
type: 'inner',
route: {
path: '/qrCode',
key: 'qrCode',
breadcrumb: '二维码管理',
component: QrCode,
// 不设置 component 则面包屑禁止跳转
// childRoutes: [
// {
// path: '/qrCode',
// key: 'qrCode',
// component: QrCode,
// breadcrumb: '二维码管理',
// },
// ]
export default [
{
type: 'inner',
route: {
path: '/qrCode',
key: 'qrCode',
breadcrumb: '二维码管理',
component: QrCode,
// 不设置 component 则面包屑禁止跳转
// childRoutes: [
// {
// path: '/qrCode',
// key: 'qrCode',
// component: QrCode,
// breadcrumb: '二维码管理',
// },
// ]
}
},
{
type: 'outer',
route: {
path: '/scan',
key: 'scan',
component: ScanCode,
breadcrumb: '扫码页面',
}
}
}];
];

131
web/client/src/utils/webapi.js

@ -4,8 +4,6 @@ import request from 'superagent';
export const ApiTable = {
login: 'login',
logout: 'logout',
validatePhone: 'validate/phone',
getUserSiteList: 'user/site/list',
// 组织管理-用户管理
getDepMessage: 'organization/department',
@ -19,131 +17,24 @@ export const ApiTable = {
delUser: 'organization/department/user/{ids}',
resetPwd: '/organization/department/user/resetPwd/{id}',
// 巡检计划
patrolPlan: 'patrolPlan', // 增改查
delPatrolPlan: 'patrolPlan/{id}',
// 巡检模板
patrolTemplate: 'patrolTemplate', // 增改查
delPatrolTemplate: 'patrolTemplate/{id}',
// 巡检记录
patrolRecord: 'patrolRecord/:patrolPlanId/:startTime/:endTime/:alarm/:pointId',
// 巡检报告
patrolReport: 'patrolReport',
// 检查项设定
checkItems: 'checkItems', // 获取/新增
updateCheckItems: 'checkItems/{id}',
delCheckItems: 'checkItems/{ids}',
checkItemsGroup: 'checkItems/group', // 获取/新增
delCheckItemsGroup: 'checkItems/group/{id}',
// 用户权限
getResource: 'resource',
getUserResource: 'user/resource',
postUserRes: 'user/resource',
//安全风险预报
getSiteWeekRegiste: 'sites/report',
getRiskReportList: 'risk/report',
modifyRiskReport: 'risk/report/{riskId}',
riskExport: 'risk/export',
getEnterprisesMembers: 'enterprises/{enterpriseId}/members',
//工程交底
getProjectDisclosureList: 'project/disclosure',
addProjectDisclosure: 'project/disclosure',
editProjectDisclosure: 'project/disclosure/{id}',
delProjectDisclosure: 'project/disclosure/{id}',
//安全巡检
getCheckTask: '/getcheckTask',
addCheckTask: '/addcheckTask',
editCheckTask: '/editcheckTask',
delCheckTask: '/delcheckTask/:id',
addPatrolRecordIssueHandle: 'patrolRecord/issue/handle',
modifyPatrolRecordIssueHandle: 'patrolRecord/issue/handle/{id}',
yujingguanli: '/yujingguanli',
//协调申请
getCoordinateList: 'risk/coordinate',
addCoordinate: 'risk/coordinate',
delCoordinate: 'risk/coordinate/{id}',
editCoordinate: 'risk/coordinate/{id}',
//会议
mettingList: 'metting/list',
editMetting: 'metting',
//隐患整改
getRectifyList: 'rectify/list',
addRectify: 'rectify',
editRectify: 'rectify/{id}',
delRectify: 'rectify/{id}',
disposeRectify: 'rectify/dispose',
rectifyReport: 'rectify/report',
//问题上报
problemReport: 'report/problem',
//查阅人员
problemReportConsult: 'report/problem/consult',
//花名册管理
getWorkerList: 'get/worker/list',
addWorker: 'add/worker',
editWorker: 'worker/{id}',
delWorker: 'worker/{id}',
getWorkerIdcards: 'worker/idcards',
//安全管理
getTring: 'training/list/:siteid',
postTring: 'training',
putTring: 'training',
delTring: 'training/:id',
//考情信息
getChcekList: 'get/worker/attendance/list',
addCheck: 'add/worker/attendance',
editCheck: 'worker/attendance/:id',
delCheck: 'worker/attendance/:id',
verifyWoker: 'verify/worker/exist',
//首页-我的待办
getDealTodoList: 'user/deal/list',
addDealTodo: 'user/deal',
//结构物
getProjectList: 'projectList',
postAddProject: 'addProject',
delProject: 'delProject/{id}',
//点位
position: 'position',
delPosition: 'delPosition/{id}',
qrCodeShow: 'qrCodeShow',
q: 'q',
//视频接入配置
siteList: 'siteList',
addCamera: 'camera',
delCamera: 'camera/{id}',
// 标签
getLabels: 'label',
createLabels: 'label/add',
delLabels: 'label/{id}',
//项目状态配置
editProjectStatus: 'project/status',
//工地平面图
getProjectGraph: 'project/{projectId}/planarGraph',
createGraph: 'planarGraph/add',
updateGraph: 'planarGraph/{id}/modify',
deleteGraph: 'project/graph/{id}',
getProjectPoints: 'project/{projectId}/all/points',
getDeployPoints: 'picture/{pictureId}/deploy/points',
setDeployPoints: 'set/picture/{pictureId}/deploy/points',
// 宣传信息配置
getPublicityInfo: 'publicityInfo',
createPublicityInfo: 'publicityInfo/add',
updatePublicityInfo: 'publicityInfo/{id}/modify',
delPublicityInfo: 'publicityInfo/{id}/del?qrcodeId={qrcodeId}',
//设备管理
getDeviceList: 'device',
addDevice: 'device',
modifyDevice: 'device/{id}',
// 二维码
getQrCode: 'qrcode',
};
export const RouteTable = {

4
web/package.json

@ -72,6 +72,7 @@
"ahooks": "^3.7.4",
"ali-oss": "^6.17.1",
"antd": "^4.24.5",
"antd-mobile": "^5.34.0",
"antd-theme-generator": "^1.2.8",
"args": "^5.0.1",
"array-move": "^3.0.1",
@ -94,11 +95,12 @@
"mini-dynamic-antd-theme": "^0.5.3",
"moment": "^2.22.0",
"npm": "^7.20.6",
"qr-code-with-logo": "^1.1.0",
"qrcode": "^1.5.1",
"qs": "^6.10.1",
"react-color": "^2.19.3",
"react-dnd": "^7",
"react-dnd-html5-backend": "^7",
"react-color": "^2.19.3",
"react-router-breadcrumbs-hoc": "^4.0.1",
"react-sortable-hoc": "^2.0.0",
"shortid": "^2.2.16",

11
web/webpack.config.js

@ -37,6 +37,7 @@ module.exports = {
module: {
rules: [{
test: /\.css$/,
exclude: path.resolve(__dirname, 'node_modules', 'antd-mobile'),
use: ['style-loader', {
loader: 'css-loader',
options: {
@ -44,6 +45,16 @@ module.exports = {
}
}]
},
{
test: /\.css$/,
include: path.resolve(__dirname, 'node_modules', 'antd-mobile'),
use: ['style-loader', {
loader: 'css-loader',
options: {
modules: false
}
}]
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', {

13
web/webpack.config.prod.js

@ -46,6 +46,7 @@ module.exports = {
module: {
rules: [{
test: /\.css$/,
exclude: path.resolve(__dirname, 'node_modules', 'antd-mobile'),
use: ['style-loader', {
loader: 'css-loader',
options: {
@ -53,6 +54,16 @@ module.exports = {
}
}]
},
{
test: /\.css$/,
include: path.resolve(__dirname, 'node_modules', 'antd-mobile'),
use: ['style-loader', {
loader: 'css-loader',
options: {
modules: false
}
}]
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader',
@ -68,7 +79,7 @@ module.exports = {
test: /\.jsx?$/,
use: 'babel-loader',
include: [PATHS.app, path.resolve(__dirname, 'node_modules', '@peace')],
},{
}, {
test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)$/,
loader: "file-loader"
}]

Loading…
Cancel
Save